Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch to poetry for project management #4

Merged
merged 11 commits into from
Aug 19, 2024
Merged
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
root = true
[*.py]
[*]
max_line_length = 88
8 changes: 8 additions & 0 deletions .github/semantic.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Config file for semantic-prs (https://github.com/Ezard/semantic-prs).
---
# PRs will be rebased before being merged. Individual commits need to
# follow the semantic PR spec.
commitsOnly: true

# Allow reverts.
allowRevertCommits: true
33 changes: 30 additions & 3 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
---
name: validate
on: push
on:
push:
branches: [main]
pull_request:
release:
types: [published]
jobs:
lint:
runs-on: ubuntu-latest
Expand All @@ -12,7 +17,29 @@ jobs:
version: 1.0
- uses: actions/setup-python@v4
with:
python-version: 3.7
python-version: 3.8
cache: pip
- run: pip install -r requirements.txt
- run: python -m pip install poetry
- run: poetry install
- run: make lint

check-types:
runs-on: macos-latest
timeout-minutes: 30
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master

- name: Install Ableton Live
run: brew install --cask ableton-live-lite

- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.8
cache: pip

- run: python -m pip install poetry
- run: poetry install
- run: make check
6 changes: 0 additions & 6 deletions .python-version

This file was deleted.

30 changes: 17 additions & 13 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
## Development

modeStep uses [poetry](https://python-poetry.org/) for project management and
development tasks. The project's `Makefile` also contains targets for common tasks.

## Testing

Tests work by opening Live, impersonating the MIDI output of the
SoftStep, and checking the messages that Live sends back.
Tests work by opening Live, impersonating the MIDI output of the SoftStep, and checking
the messages that Live sends back.

To get this working, you need to configure a "modeStep" control
surface to use "modeStep test" as its inputs and outputs. However,
that source won't show up until tests are actually running, so you'll
need to configure the control surface manually while you're running
tests for the first time.
To get this working, you need to configure a "modeStep" control surface to use "modeStep
test" as its inputs and outputs. However, that source won't show up until tests are
actually running, so you'll need to configure the control surface manually while you're
running tests for the first time.

You can safely add this test control surface alongside your primary
modeStep control surface, if you don't want to deal with switching the
input/output when you want to run tests.
You can safely add this test control surface alongside your primary modeStep control
surface, if you don't want to deal with switching the input/output when you want to run
tests.

To run tests, use:

Expand All @@ -25,10 +29,10 @@ For debug output:
DEBUG=1 make test
```

To run only specs tagged with `@now`:
To run only e.g. specs tagged with `@now`:

```shell
.venv/bin/pytest -m now
poetry run pytest -m now
```

## Linting and type checks
Expand All @@ -43,5 +47,5 @@ make check # Validates types.
Some lint errors can be fixed automatically with:

```shell
make fix
make format
```
67 changes: 42 additions & 25 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,56 +1,73 @@
SYSTEM_MIDI_REMOTE_SCRIPTS_DIR := /Applications/Ableton\ Live\ 12\ Suite.app/Contents/App-Resources/MIDI\ Remote\ Scripts
POETRY := $(shell command -v poetry 2> /dev/null)

# Recognize "Ableton Live 12 Suite", "Ableton Live 12 Lite", etc. Escape whitespace int
# he result so that we can use this as a target.
SYSTEM_MIDI_REMOTE_SCRIPTS_DIR := $(shell ls -d /Applications/Ableton\ Live\ 12\ *.app/Contents/App-Resources/MIDI\ Remote\ Scripts 2> /dev/null | head -n 1 | sed 's/ /\\ /g')

TEST_PROJECT_SET_NAMES := backlight default overrides standalone wide_clip_launch
TEST_PROJECT_DIR := tests/modeStep_tests_project
TEST_PROJECT_SETS := $(addprefix $(TEST_PROJECT_DIR)/, $(addsuffix .als, $(TEST_PROJECT_SET_NAMES)))

.PHONY: deps
deps: __ext__/System_MIDIRemoteScripts/.make.decompile .make.pip-install
deps: .make.install __ext__/System_MIDIRemoteScripts/.make.decompile

.PHONY: lint
lint: .make.pip-install
.venv/bin/ruff format --check .
.venv/bin/ruff check .
lint: .make.install
$(POETRY) run ruff format --check .
$(POETRY) run ruff check .

.PHONY: fix
fix: .make.pip-install
.venv/bin/ruff format .
.venv/bin/ruff check --fix .
format: .make.install
$(POETRY) run ruff format .
$(POETRY) run ruff check --fix .

.PHONY: check
check: .make.pip-install __ext__/System_MIDIRemoteScripts/.make.decompile
.venv/bin/pyright .
check: .make.install __ext__/System_MIDIRemoteScripts/.make.decompile
$(POETRY) run pyright .

.PHONY: test
test: .make.pip-install $(TEST_PROJECT_SETS)
.venv/bin/pytest
test: .make.install $(TEST_PROJECT_SETS)
$(POETRY) run pytest

.PHONY: img
img: .make.pip-install
cd img && ../.venv/bin/python generate.py
img: .make.install
$(POETRY) run python img/generate.py

.PHONY: clean
clean:
rm -rf .venv/
rm -rf __ext__/System_MIDIRemoteScripts/
rm -f .make.*
# The .venv folder gets created by poetry (because virtualenvs.in-project is enabled).
rm -rf .venv/
rm -f .make.install

# Set files with different configurations for testing.
$(TEST_PROJECT_DIR)/%.als: .make.pip-install $(TEST_PROJECT_DIR)/create_set.py
.venv/bin/python $(TEST_PROJECT_DIR)/create_set.py $*
$(TEST_PROJECT_DIR)/%.als: .make.install $(TEST_PROJECT_DIR)/create_set.py
$(POETRY) run python $(TEST_PROJECT_DIR)/create_set.py $*

__ext__/System_MIDIRemoteScripts/.make.decompile: $(SYSTEM_MIDI_REMOTE_SCRIPTS_DIR) | .make.pip-install
__ext__/System_MIDIRemoteScripts/.make.decompile: $(SYSTEM_MIDI_REMOTE_SCRIPTS_DIR) | .make.install
# Sanity check before rm'ing.
@if [ -z "$(@D)" ]; then \
echo "Sanity check failed: compile dir is not set"; \
exit 1; \
fi
rm -rf $(@D)/
mkdir -p $(@D)/ableton/
@if [ -z $(SYSTEM_MIDI_REMOTE_SCRIPTS_DIR) ]; then \
echo "System remote scripts directory not found" ; \
exit 1; \
fi
@if [ ! -d $(SYSTEM_MIDI_REMOTE_SCRIPTS_DIR) ]; then \
echo "The specified remote scripts directory ("$(SYSTEM_MIDI_REMOTE_SCRIPTS_DIR)") does not exist"; \
exit 1; \
fi

# decompyle3 works for most files, and the ones where it doesn't don't
# matter for our purposes.
.venv/bin/decompyle3 -r -o $(@D)/ableton/ $(SYSTEM_MIDI_REMOTE_SCRIPTS_DIR)/ableton/
$(POETRY) run decompyle3 -r -o $(@D)/ableton/ $(SYSTEM_MIDI_REMOTE_SCRIPTS_DIR)/ableton/

touch $@

.venv: .python-version
python -m venv .venv

.make.pip-install: .venv requirements.txt .python-version
.venv/bin/pip install -r requirements.txt
.make.install: pyproject.toml poetry.lock
@if [ -z $(POETRY) ]; then echo "Poetry could not be found. See https://python-poetry.org/docs/"; exit 2; fi
$(POETRY) install
touch $@
7 changes: 5 additions & 2 deletions control_surface/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,19 +294,22 @@ def override_nav(


def _override_elements_with_nav(
# Names of elements representing each direction.
left: str,
right: str,
up: str,
down: str,
# Nav to apply in the horizontal/vertical directions, or None if no nav should be specified.
horizontal: Optional[NavigationTarget],
vertical: Optional[NavigationTarget],
) -> List[ElementOverride]:
results: List[ElementOverride] = []
for down_element, up_element, target in (
elements_and_targets: Iterable[Tuple[str, str, Optional[NavigationTarget]]] = (
(left, right, horizontal),
# The down button increases values - think selected scene.
(up, down, vertical),
):
)
for down_element, up_element, target in elements_and_targets:
# Get a unique control name based on an element name.
def background_control_name(element: str):
return element.replace("[", "_").replace("]", "_")
Expand Down
17 changes: 15 additions & 2 deletions control_surface/elements/elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,19 @@
from .sysex import SysexButtonElement, SysexToggleElement

if TYPE_CHECKING:
from typing_extensions import NotRequired

from ..configuration import Configuration

AddSubmatrixKwargs = TypedDict(
"AddSubmatrixKwargs",
{
"rows": NotRequired[Iterable[int]],
"columns": NotRequired[Iterable[int]],
"is_private": NotRequired[bool],
},
)

logger = getLogger(__name__)

LATCH_DELAY = TIMER_DELAY * 2
Expand Down Expand Up @@ -244,7 +255,7 @@ def add_submatrices(
grid: ButtonMatrixElement = getattr(self, grid_submatrix_name)

# Segments.
for segment_name, attrs in {
segment_names_to_attrs: Dict[str, AddSubmatrixKwargs] = {
"top": {"rows": (0, 1)},
"bottom": {"rows": (1, 2)},
"left": {"columns": (0, int(NUM_GRID_COLS * controls_per_key / 2))},
Expand All @@ -254,7 +265,9 @@ def add_submatrices(
NUM_GRID_COLS * controls_per_key,
)
},
}.items():
}

for segment_name, attrs in segment_names_to_attrs.items():
segment_submatrix_name = f"grid_{segment_name}_{category_base_name}"
self.add_submatrix(grid, segment_submatrix_name, **attrs)

Expand Down
5 changes: 3 additions & 2 deletions control_surface/mappings.py
Original file line number Diff line number Diff line change
Expand Up @@ -456,12 +456,13 @@ def _navigation_mode(
vertical_target: Optional[NavigationTarget],
) -> List[SimpleModeSpecification]:
component_mappings: Dict[str, Dict[str, str]] = {}
for navigation_target, down_button, up_button in (
targets_and_elements: Iterable[Tuple[Optional[NavigationTarget], str, str]] = (
(horizontal_target, "nav_left_button", "nav_right_button"),
# The down button generally increases values, e.g. the selected scene index,
# session ring position...
(vertical_target, "nav_up_button", "nav_down_button"),
):
)
for navigation_target, down_button, up_button in targets_and_elements:
if navigation_target:
component, down_attribute, up_attribute = NAVIGATION_TARGET_MAPPINGS[
navigation_target
Expand Down
Loading
Loading