Skip to content

Commit 9b36379

Browse files
authored
Merge pull request #247 from DuguidLab/celefthe/issue246
Migrate linting & formatting to ruff
2 parents 45e97f3 + 1be6810 commit 9b36379

34 files changed

Lines changed: 286 additions & 469 deletions

.coveragerc

Lines changed: 0 additions & 3 deletions
This file was deleted.
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ on:
88

99
jobs:
1010
lint:
11-
name: "lint"
11+
name: "ruff"
1212
runs-on: ubuntu-latest
1313
steps:
1414
- uses: actions/checkout@v4
15-
- uses: psf/black@stable
15+
- uses: astral-sh/ruff-action@v3
1616
with:
1717
src: "./src"
18-
# use_pyproject: true
18+
# - run: ruff check
19+
# - run: ruff format

.pre-commit-config.yaml

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,16 @@ repos:
77
- id: check-merge-conflict
88
- id: debug-statements
99
- id: no-commit-to-branch
10-
- repo: https://github.com/psf/black
11-
rev: 23.10.1
12-
hooks:
13-
- id: black
10+
# - repo: https://github.com/psf/black
11+
# rev: 23.10.1
12+
# hooks:
13+
# - id: black
14+
- repo: https://github.com/astral-sh/ruff-pre-commit
15+
# Ruff version.
16+
rev: v0.8.6
17+
hooks:
18+
# Run the linter.
19+
# - id: ruff
20+
# args: [ --fix ]
21+
# Run the formatter.
22+
- id: ruff-format

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Versions follow [Semantic Versioning](https://semver.org) (`<major>.<minor>.<pat
1212
- Update Flask and Werkzeug to >v3.0 ([#227](https://github.com/DuguidLab/visiomode/issues/227)).
1313
- Changed testing framework to pytest ([[#239](https://github.com/DuguidLab/visiomode/issues/239)], [[#242](https://github.com/DuguidLab/visiomode/issues/242)]).
1414
- Enhanced test coverage ([[#240](https://github.com/DuguidLab/visiomode/issues/240)], [[#211](https://github.com/DuguidLab/visiomode/issues/211)], [[#71](https://github.com/DuguidLab/visiomode/issues/71)], [[#72](https://github.com/DuguidLab/visiomode/issues/72)]).
15+
- Switch to `ruff` for linting and formatting ([[[#246](https://github.com/DuguidLab/visiomode/issues/246)]]).
1516

1617
## [0.6.0] - 2024-08-29
1718

MANIFEST.in

Lines changed: 0 additions & 1 deletion
This file was deleted.

pylintrc

Lines changed: 0 additions & 36 deletions
This file was deleted.

pyproject.toml

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,6 @@ path = "src/visiomode/__about__.py"
6060
[tool.hatch.envs.default]
6161
python = "3.9"
6262

63-
[tool.hatch.envs.develop]
64-
python = "3.9"
65-
dependencies = ["black", "pylint", "pre-commit"]
66-
6763
[tool.hatch.envs.docs]
6864
dependencies = [
6965
"mkdocs",
@@ -92,25 +88,23 @@ python = ["3.9", "3.10", "3.11", "3.12"]
9288

9389
[tool.hatch.envs.test.scripts]
9490
run-coverage = "pytest --cov-config=pyproject.toml --cov=visiomode --cov=tests --cov-report term --cov-report xml:coverage.xml"
95-
run-coverage-ci = "run-coverage --cov-report=xml"
91+
run-coverage-ci = "run-coverage --cov-report=xml:coverage.xml"
9692
run = "run-coverage --no-cov"
9793

9894
[tool.hatch.envs.lint]
9995
detached = true
10096
dependencies = [
101-
"black>=23.1.0",
10297
"mypy>=1.0.0",
10398
"ruff>=0.0.243",
10499
]
105100
[tool.hatch.envs.lint.scripts]
106101
typing = "mypy --install-types --non-interactive {args:src/visiomode tests}"
107102
style = [
108-
"ruff {args:.}",
109-
"black --check --diff {args:.}",
103+
"ruff check {args:src/visiomode tests}",
110104
]
111105
fmt = [
112-
"black {args:.}",
113-
"ruff --fix {args:.}",
106+
"ruff check --fix {args:src/visiomode tests}",
107+
"ruff format {args:src/visiomode tests}",
114108
"style",
115109
]
116110
all = [
@@ -119,9 +113,9 @@ all = [
119113
]
120114

121115
[tool.ruff]
122-
target-version = "py311"
116+
target-version = "py39"
123117
line-length = 120
124-
select = [
118+
lint.select = [
125119
"A",
126120
"ARG",
127121
"B",
@@ -148,27 +142,34 @@ select = [
148142
"W",
149143
"YTT",
150144
]
151-
ignore = [
145+
lint.ignore = [
152146
# Allow non-abstract empty methods in abstract base classes
153147
"B027",
154148
# Allow boolean positional values in function calls, like `dict.get(... True)`
155149
"FBT003",
156150
# Ignore checks for possible passwords
157151
"S105", "S106", "S107",
158152
# Ignore complexity
159-
"C901", "PLR0911", "PLR0912", "PLR0913", "PLR0915",
160-
]
161-
unfixable = [
162-
# Don't touch unused imports
163-
"F401",
153+
"C901", "PLR0911", "PLR0912", "PLR0913", "PLR0915", "ISC001",
154+
# Ignore binding to all interfaces
155+
"S104",
156+
# Ignore timezone guff
157+
"DTZ005"
164158
]
159+
# unfixable = [
160+
# # Don't touch unused imports
161+
# "F401",
162+
# ]
165163

166-
[tool.ruff.isort]
164+
[tool.ruff.lint.isort]
167165
known-first-party = ["visiomode"]
168166

169-
[tool.ruff.flake8-tidy-imports]
167+
[tool.ruff.lint.flake8-tidy-imports]
170168
ban-relative-imports = "all"
171169

172-
[tool.ruff.per-file-ignores]
173-
# Tests can use magic values, assertions, and relative imports
174-
"tests/**/*" = ["PLR2004", "S101", "TID252"]
170+
[tool.ruff.lint.flake8-unused-arguments]
171+
ignore-variadic-names = true
172+
173+
[tool.ruff.lint.per-file-ignores]
174+
# Tests can use magic values, assertions, relative and unused imports, and unused arguments
175+
"tests/**/*" = ["PLR2004", "S101", "TID252", "F401", "ARG001"]

src/visiomode/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
# Distributed under the terms of the MIT Licence.
66

77
import faulthandler # report segmentation faults as tracebacks
8-
import visiomode.core
98

10-
from visiomode.__about__ import __version__
9+
import visiomode.core
10+
from visiomode.__about__ import __version__ # noqa: F401
1111

1212
faulthandler.enable()
1313

src/visiomode/config.py

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,17 @@
2727
"height": 800,
2828
"fullscreen": False,
2929
"devices": "devices",
30-
"input_device_address": (
31-
ports.comports()[1].device if len(ports.comports()) > 1 else "/dev/ttyS0"
32-
),
33-
"reward_device_address": (
34-
ports.comports()[0].device if len(ports.comports()) else "/dev/ttyS0"
35-
),
30+
"input_device_address": (ports.comports()[1].device if len(ports.comports()) > 1 else "/dev/ttyS0"),
31+
"reward_device_address": (ports.comports()[0].device if len(ports.comports()) else "/dev/ttyS0"),
3632
"config_path": ".visiomode.json",
3733
}
3834

3935

4036
class Config:
4137
"""App configuration class.
4238
43-
This class loads and saves configuration from a JSON file. If no config file exists, it creates one with default values.
39+
This class loads and saves configuration from a JSON file. If no config file exists, it creates one with default
40+
values.
4441
4542
Attributes:
4643
debug: Debug mode flag.
@@ -91,7 +88,7 @@ def __new__(
9188
DEFAULT_CONFIG_PATH. Only used if it exists.
9289
"""
9390
if cls._instance is None:
94-
cls._instance = super(Config, cls).__new__(cls)
91+
cls._instance = super().__new__(cls)
9592

9693
if config_path is None:
9794
config_path = DEFAULT_CONFIG["config_path"]
@@ -108,9 +105,7 @@ def __new__(
108105
os.makedirs(cls._instance.cache_dir, exist_ok=True)
109106
os.makedirs(cls._instance.db_dir, exist_ok=True)
110107
elif config_path is not None:
111-
logging.warning(
112-
"Config has already been loaded, ignoring `config_path` passed to constructor."
113-
)
108+
logging.warning("Config has already been loaded, ignoring `config_path` passed to constructor.")
114109

115110
return cls._instance
116111

@@ -132,14 +127,14 @@ def _load_config(self, config_path: str):
132127
Args:
133128
config_path: Path to config JSON file.
134129
"""
135-
with open(config_path, "r") as f:
130+
with open(config_path) as f:
136131
config = json.load(f)
137132

138133
for key, value in config.items():
139134
if key in DEFAULT_CONFIG:
140135
setattr(self, key, value)
141136
else:
142-
logging.warning("Unknown config key: {}".format(key))
137+
logging.warning(f"Unknown config key: {key}")
143138

144139
self._validate()
145140

@@ -151,9 +146,7 @@ def _set_defaults(self):
151146
def _validate(self) -> None:
152147
for key, value in DEFAULT_CONFIG.items():
153148
if not hasattr(self, key):
154-
logging.warning(
155-
f"Config is malformed, it does not have attribute '{key}'. Using default value."
156-
)
149+
logging.warning(f"Config is malformed, it does not have attribute '{key}'. Using default value.")
157150
setattr(self, key, value)
158151

159152

src/visiomode/core.py

Lines changed: 17 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,18 @@
55
# Copyright (c) 2024 Olivier Delree <odelree@ed.ac.uk>
66
# Distributed under the terms of the MIT Licence.
77

8-
import importlib.resources as resources
9-
import os
10-
import logging
11-
import time
128
import datetime
13-
import threading
9+
import logging
10+
import os
1411
import queue
12+
import threading
13+
import time
14+
from importlib import resources
1515

1616
import pygame as pg
1717

1818
import visiomode.config as conf
19-
import visiomode.devices as devices
20-
import visiomode.models as models
21-
import visiomode.plugins as plugins
22-
import visiomode.stimuli as stimuli
23-
import visiomode.tasks as tasks
24-
import visiomode.webpanel as webpanel
19+
from visiomode import devices, models, plugins, stimuli, tasks, webpanel
2520

2621
# Register mouse events as touch events - useful for debugging.
2722
os.environ["SDL_MOUSE_TOUCH_EVENTS"] = "1"
@@ -37,9 +32,9 @@ class Visiomode:
3732

3833
def __init__(
3934
self,
40-
run_application_loop: bool = True,
41-
run_webpanel: bool = True,
42-
load_plugins: bool = True,
35+
# run_application_loop: bool = True,
36+
# run_webpanel: bool = True,
37+
# load_plugins: bool = True,
4338
):
4439
"""Initialise application.
4540
@@ -90,7 +85,7 @@ def run(self) -> None:
9085

9186
self.run_main()
9287

93-
def loading_screen(self):
88+
def loading_screen(self, max_angle=1080, angle_rotation=5):
9489
"""Rotating logo to entertain user and mouse while the webpanel is loading."""
9590
# Fill background
9691
self.background = pg.Surface(self.screen.get_size())
@@ -122,13 +117,13 @@ def loading_screen(self):
122117
pg.display.flip()
123118

124119
angle = 0
125-
while angle != 1080:
120+
while angle != max_angle:
126121
events = pg.event.get()
127122
for event in events:
128123
if event.type == pg.QUIT:
129124
return
130125

131-
angle += 5
126+
angle += angle_rotation
132127

133128
image, rect = rotate(loading_img, loading_img_pos, angle)
134129

@@ -143,9 +138,7 @@ def loading_screen(self):
143138

144139
textpos = text.get_rect()
145140
textpos.centerx = self.background.get_rect().centerx
146-
textpos.centery = (
147-
self.background.get_rect().centery + 60
148-
) # TODO calculate offset at runtime
141+
textpos.centery = self.background.get_rect().centery + 60 # TODO calculate offset at runtime
149142

150143
self.background.blit(text, textpos)
151144
self.screen.blit(self.background, (0, 0))
@@ -167,8 +160,7 @@ def run_main(self):
167160
self.session.trials = self.session.task.trials
168161
if self.session and (
169162
not self.session.task.is_running
170-
or time.time() - self.session.task.start_time
171-
> self.session.duration * 60
163+
or time.time() - self.session.task.start_time > self.session.duration * 60
172164
):
173165
logging.info("Session finished.")
174166
self.session.task.stop()
@@ -201,17 +193,15 @@ def request_listener(self):
201193
while True:
202194
request = self.action_q.get()
203195
if "type" not in request.keys():
204-
logging.error("Invalid request - {}".format(request))
196+
logging.error(f"Invalid request - {request}")
205197
continue
206198
if request["type"] == "start":
207199
# Update config
208200
conf.Config().input_device_address = (
209-
request["data"].get("response_address")
210-
or conf.Config().input_device_address
201+
request["data"].get("response_address") or conf.Config().input_device_address
211202
)
212203
conf.Config().reward_device_address = (
213-
request["data"].get("reward_address")
214-
or conf.Config().reward_device_address
204+
request["data"].get("reward_address") or conf.Config().reward_device_address
215205
)
216206
conf.Config().save()
217207

0 commit comments

Comments
 (0)