Skip to content

Commit bf767e8

Browse files
vpeterssonclaude
andcommitted
feat: modernize Python codebase to 3.13+ minimum
- Set minimum Python version to 3.13, remove Python 2 compatibility - Remove legacy imports: future, six, builtins, __future__, configparser, importlib-metadata, pep8, mock, unittest-parametrize - Add modern type hints throughout (PEP 604 union syntax, PEP 585 generics) - Replace .format() with f-strings, use super() without args - Replace pytz with datetime.timezone, distutils.strtobool with inline impl - Use conditional import for cec hardware dependency - Clean up uv.lock (6 packages removed) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d89ee67 commit bf767e8

28 files changed

+202
-693
lines changed

.python-version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3.11
1+
3.13

anthias_app/consumers.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88

99
class ViewerConsumer(WebSocketConsumer):
10-
def connect(self):
10+
def connect(self) -> None:
1111
from channels.layers import get_channel_layer
1212

1313
self.accept()
@@ -20,7 +20,7 @@ def connect(self):
2020
)
2121
logger.info('Viewer WebSocket connected')
2222

23-
def disconnect(self, close_code):
23+
def disconnect(self, close_code: int) -> None:
2424
from channels.layers import get_channel_layer
2525

2626
channel_layer = get_channel_layer()
@@ -32,15 +32,15 @@ def disconnect(self, close_code):
3232
)
3333
logger.info('Viewer WebSocket disconnected')
3434

35-
def viewer_command(self, event):
35+
def viewer_command(self, event: dict) -> None:
3636
self.send(text_data=json.dumps({
3737
'command': event['command'],
3838
'data': event.get('data'),
3939
}))
4040

4141

4242
class UIConsumer(WebSocketConsumer):
43-
def connect(self):
43+
def connect(self) -> None:
4444
from channels.layers import get_channel_layer
4545

4646
self.accept()
@@ -52,7 +52,7 @@ def connect(self):
5252
'ui', self.channel_name
5353
)
5454

55-
def disconnect(self, close_code):
55+
def disconnect(self, close_code: int) -> None:
5656
from channels.layers import get_channel_layer
5757

5858
channel_layer = get_channel_layer()
@@ -63,5 +63,5 @@ def disconnect(self, close_code):
6363
'ui', self.channel_name
6464
)
6565

66-
def ui_update(self, event):
66+
def ui_update(self, event: dict) -> None:
6767
self.send(text_data=json.dumps(event.get('data', {})))

anthias_app/helpers.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from settings import settings
1212

1313

14-
def template(request, template_name, context):
14+
def template(request, template_name: str, context: dict):
1515
"""
1616
This is a helper function that is used to render a template
1717
with some global context. This is used to avoid having to
@@ -33,11 +33,11 @@ def template(request, template_name, context):
3333
return render(request, template_name, context)
3434

3535

36-
def prepare_default_asset(**kwargs):
36+
def prepare_default_asset(**kwargs) -> dict | None:
3737
if kwargs['mimetype'] not in ['image', 'video', 'webpage']:
3838
return
3939

40-
asset_id = 'default_{}'.format(uuid.uuid4().hex)
40+
asset_id = f'default_{uuid.uuid4().hex}'
4141
duration = (
4242
int(get_video_duration(kwargs['uri']).total_seconds())
4343
if 'video' == kwargs['mimetype']
@@ -60,7 +60,7 @@ def prepare_default_asset(**kwargs):
6060
}
6161

6262

63-
def add_default_assets():
63+
def add_default_assets() -> None:
6464
settings.load()
6565

6666
datetime_now = timezone.now()
@@ -75,7 +75,7 @@ def add_default_assets():
7575
'.screenly/default_assets.yml',
7676
)
7777

78-
with open(default_assets_yaml, 'r') as yaml_file:
78+
with open(default_assets_yaml) as yaml_file:
7979
default_assets = yaml.safe_load(yaml_file).get('assets')
8080

8181
for default_asset in default_assets:
@@ -92,7 +92,7 @@ def add_default_assets():
9292
Asset.objects.create(**asset)
9393

9494

95-
def remove_default_assets():
95+
def remove_default_assets() -> None:
9696
settings.load()
9797

9898
for asset in Asset.objects.all():

anthias_app/messaging.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
logger = logging.getLogger(__name__)
44

55

6-
def send_to_viewer(command, data=None):
6+
def send_to_viewer(
7+
command: str, data: str | None = None,
8+
) -> None:
79
try:
810
from asgiref.sync import async_to_sync
911
from channels.layers import get_channel_layer
@@ -22,7 +24,7 @@ def send_to_viewer(command, data=None):
2224
logger.exception('Failed to send to viewer')
2325

2426

25-
def send_to_ui(data):
27+
def send_to_ui(data: dict) -> None:
2628
try:
2729
from asgiref.sync import async_to_sync
2830
from channels.layers import get_channel_layer

anthias_app/tasks.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,19 @@
1919
_scheduler_lock = threading.Lock()
2020

2121

22-
def get_display_power_value():
22+
def get_display_power_value() -> str | bool | None:
2323
return _display_power
2424

2525

26-
def _update_display_power():
26+
def _update_display_power() -> None:
2727
global _display_power
2828
try:
2929
_display_power = diagnostics.get_display_power()
3030
except Exception:
3131
logger.exception('Failed to get display power')
3232

3333

34-
def cleanup():
34+
def cleanup() -> None:
3535
try:
3636
asset_dir = path.join(getenv('HOME', ''), 'screenly_assets')
3737
if path.isdir(asset_dir):
@@ -40,7 +40,7 @@ def cleanup():
4040
logger.exception('Cleanup failed')
4141

4242

43-
def reboot_anthias():
43+
def reboot_anthias() -> None:
4444
if is_balena_app():
4545
for attempt in Retrying(
4646
stop=stop_after_attempt(5),
@@ -54,7 +54,7 @@ def reboot_anthias():
5454
call(['reboot'])
5555

5656

57-
def shutdown_anthias():
57+
def shutdown_anthias() -> None:
5858
if is_balena_app():
5959
for attempt in Retrying(
6060
stop=stop_after_attempt(5),
@@ -68,7 +68,9 @@ def shutdown_anthias():
6868
call(['shutdown', '-h', 'now'])
6969

7070

71-
def _run_periodic(func, interval_seconds, name):
71+
def _run_periodic(
72+
func, interval_seconds: int, name: str,
73+
) -> threading.Thread:
7274
def loop():
7375
while True:
7476
try:
@@ -82,7 +84,7 @@ def loop():
8284
return t
8385

8486

85-
def start_background_scheduler():
87+
def start_background_scheduler() -> None:
8688
global _scheduler_started
8789
with _scheduler_lock:
8890
if _scheduler_started:

anthias_app/views_ui.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ def system_info_data(request):
9393

9494
git_branch = diagnostics.get_git_branch()
9595
git_short_hash = diagnostics.get_git_short_hash()
96-
anthias_version = '{}@{}'.format(git_branch, git_short_hash)
96+
anthias_version = f'{git_branch}@{git_short_hash}'
9797

9898
ip_addresses = []
9999
import ipaddress
@@ -103,9 +103,9 @@ def system_info_data(request):
103103
for ip in node_ip.split():
104104
ip_obj = ipaddress.ip_address(ip)
105105
if isinstance(ip_obj, ipaddress.IPv6Address):
106-
ip_addresses.append('http://[{}]'.format(ip))
106+
ip_addresses.append(f'http://[{ip}]')
107107
else:
108-
ip_addresses.append('http://{}'.format(ip))
108+
ip_addresses.append(f'http://{ip}')
109109

110110
info = {
111111
'loadavg': diagnostics.get_load_avg()['15 min'],

api/views/mixins.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import threading
12
import uuid
23
from base64 import b64encode
34
from inspect import cleandoc
@@ -18,7 +19,6 @@
1819
RebootViewSerializerMixin,
1920
ShutdownViewSerializerMixin,
2021
)
21-
import threading
2222

2323
from anthias_app.messaging import send_to_viewer
2424
from anthias_app.tasks import get_display_power_value, reboot_anthias, shutdown_anthias

bin/wait.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
from __future__ import print_function, unicode_literals
2-
31
import time
4-
from builtins import range
52

63
import sh
74

conftest.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import os
22

3-
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'anthias_django.settings')
3+
os.environ.setdefault(
4+
'DJANGO_SETTINGS_MODULE', 'anthias_django.settings'
5+
)
46
os.environ.setdefault('ENVIRONMENT', 'test')

host_agent.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11
#!/usr/bin/env python3
2-
# -*- coding: utf-8 -*-
3-
4-
from __future__ import unicode_literals
52

63
__author__ = 'Nash Kaminski'
74
__license__ = 'Dual License: GPLv2 and Commercial License'

0 commit comments

Comments
 (0)