Compare commits

..

4 Commits

15 changed files with 184 additions and 420 deletions
+1 -2
View File
@@ -25,8 +25,7 @@ env:
jobs: jobs:
preview: preview:
if: false # tmp disable due to GH API rate limiting flakiness if: github.repository == 'sunnypilot/sunnypilot'
#if: github.repository == 'sunnypilot/sunnypilot'
name: preview name: preview
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 20 timeout-minutes: 20
@@ -49,8 +49,9 @@ def get_cruise_accel(e2e, v_cruise, v_ego, a_cruise_prev, angle_steers, CP, dt,
max_accel = min(max_accel, coast_limit) max_accel = min(max_accel, coast_limit)
target_accel = np.clip(v_cruise - v_ego, A_CRUISE_MIN, max_accel) target_accel = np.clip(v_cruise - v_ego, A_CRUISE_MIN, max_accel)
j_cruise = np.interp(v_ego, A_CRUISE_MAX_BP, J_CRUISE_VALS) if not e2e:
target_accel = float(np.clip(target_accel, a_cruise_prev - j_cruise * dt, a_cruise_prev + j_cruise * dt)) j_cruise = np.interp(v_ego, A_CRUISE_MAX_BP, J_CRUISE_VALS)
target_accel = float(np.clip(target_accel, a_cruise_prev - j_cruise * dt, a_cruise_prev + j_cruise * dt))
return target_accel return target_accel
@@ -64,9 +65,10 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
self.dt = dt self.dt = dt
self.allow_throttle = True self.allow_throttle = True
self.a_desired = init_a
self.v_desired_filter = FirstOrderFilter(init_v, 2.0, self.dt) self.v_desired_filter = FirstOrderFilter(init_v, 2.0, self.dt)
self.a_cruise = init_a self.a_cruise = 0.0
self.output_a_target = init_a self.output_a_target = 0.0
self.output_should_stop = False self.output_should_stop = False
self.v_desired_trajectory = np.zeros(CONTROL_N) self.v_desired_trajectory = np.zeros(CONTROL_N)
@@ -103,8 +105,7 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
if reset_state: if reset_state:
self.v_desired_filter.x = v_ego self.v_desired_filter.x = v_ego
self.output_a_target = np.clip(sm['carState'].aEgo, ACCEL_MIN, ACCEL_MAX) self.a_desired = np.clip(sm['carState'].aEgo, ACCEL_MIN, ACCEL_MAX)
self.a_cruise = self.output_a_target
# Prevent divergence, smooth in current v_ego # Prevent divergence, smooth in current v_ego
self.v_desired_filter.x = max(0.0, self.v_desired_filter.update(v_ego)) self.v_desired_filter.x = max(0.0, self.v_desired_filter.update(v_ego))
@@ -112,11 +113,11 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
# No change cost when user is controlling the speed, or when standstill # No change cost when user is controlling the speed, or when standstill
prev_accel_constraint = not (reset_state or sm['carState'].standstill) prev_accel_constraint = not (reset_state or sm['carState'].standstill)
# Get new v_cruise and a_target from Smart Cruise Control and Speed Limit Assist # Get new v_cruise and a_desired from Smart Cruise Control and Speed Limit Assist
v_cruise, self.output_a_target = LongitudinalPlannerSP.update_targets(self, sm, self.v_desired_filter.x, self.output_a_target, v_cruise) v_cruise, self.a_desired = LongitudinalPlannerSP.update_targets(self, sm, self.v_desired_filter.x, self.a_desired, v_cruise)
self.mpc.set_weights(prev_accel_constraint, personality=sm['selfdriveState'].personality) self.mpc.set_weights(prev_accel_constraint, personality=sm['selfdriveState'].personality)
self.mpc.set_cur_state(self.v_desired_filter.x, self.output_a_target) self.mpc.set_cur_state(self.v_desired_filter.x, self.a_desired)
self.mpc.update(sm['radarState'], personality=sm['selfdriveState'].personality) self.mpc.update(sm['radarState'], personality=sm['selfdriveState'].personality)
self.v_desired_trajectory = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.v_solution) self.v_desired_trajectory = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.v_solution)
@@ -129,7 +130,7 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
cloudlog.info("FCW triggered") cloudlog.info("FCW triggered")
# Save starting point for next iteration # Save starting point for next iteration
a_prev = self.output_a_target a_prev = self.a_desired
action_t = self.CP.longitudinalActuatorDelay + DT_MDL action_t = self.CP.longitudinalActuatorDelay + DT_MDL
output_a_target_mpc = get_accel_from_plan(self.v_desired_trajectory, self.a_desired_trajectory, CONTROL_N_T_IDX, output_a_target_mpc = get_accel_from_plan(self.v_desired_trajectory, self.a_desired_trajectory, CONTROL_N_T_IDX,
@@ -154,6 +155,7 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
self.output_should_stop = any(should_stop for _, _, should_stop in candidates) self.output_should_stop = any(should_stop for _, _, should_stop in candidates)
self.output_a_target = np.clip(output_a_target, ACCEL_MIN, ACCEL_MAX) self.output_a_target = np.clip(output_a_target, ACCEL_MIN, ACCEL_MAX)
self.a_desired = float(self.output_a_target)
self.v_desired_filter.x = self.v_desired_filter.x + self.dt * (self.output_a_target + a_prev) / 2.0 self.v_desired_filter.x = self.v_desired_filter.x + self.dt * (self.output_a_target + a_prev) / 2.0
def publish(self, sm, pm): def publish(self, sm, pm):
@@ -22,9 +22,9 @@ from openpilot.system.ui.widgets.toggle import ON_COLOR
from openpilot.sunnypilot.models.runners.constants import CUSTOM_MODEL_PATH from openpilot.sunnypilot.models.runners.constants import CUSTOM_MODEL_PATH
from openpilot.system.ui.sunnypilot.lib.styles import style from openpilot.system.ui.sunnypilot.lib.styles import style
from openpilot.system.ui.sunnypilot.lib.utils import NoElideButtonAction, ScrollingButtonAction from openpilot.system.ui.sunnypilot.lib.utils import NoElideButtonAction
from openpilot.system.ui.sunnypilot.widgets.list_view import ListItemSP, toggle_item_sp, option_item_sp from openpilot.system.ui.sunnypilot.widgets.list_view import ListItemSP, toggle_item_sp, option_item_sp
from openpilot.system.ui.sunnypilot.widgets.download_status import download_status_item from openpilot.system.ui.sunnypilot.widgets.progress_bar import progress_item
from openpilot.system.ui.sunnypilot.widgets.tree_dialog import TreeOptionDialog, TreeNode, TreeFolder from openpilot.system.ui.sunnypilot.widgets.tree_dialog import TreeOptionDialog, TreeNode, TreeFolder
if gui_app.sunnypilot_ui(): if gui_app.sunnypilot_ui():
@@ -35,8 +35,9 @@ class ModelsLayout(Widget):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.model_manager = None self.model_manager = None
self.download_status = None
self.prev_download_status = None
self.model_dialog = None self.model_dialog = None
self._downloading = False
self.last_cache_calc_time = 0 self.last_cache_calc_time = 0
self._initialize_items() self._initialize_items()
@@ -51,11 +52,15 @@ class ModelsLayout(Widget):
self.current_model_item = ListItemSP( self.current_model_item = ListItemSP(
title=tr("Current Model"), title=tr("Current Model"),
description="", description="",
action_item=ScrollingButtonAction(tr("SELECT")), action_item=NoElideButtonAction(tr("SELECT")),
callback=self._handle_current_model_clicked callback=self._handle_current_model_clicked
) )
self.download_item = download_status_item(lambda: tr("Download") if self._downloading else tr("Model Status")) self.supercombo_label = progress_item(tr("Driving Model"))
self.vision_label = progress_item(tr("Vision Model"))
self.policy_label = progress_item(tr("Policy Model"))
self.off_policy_label = progress_item(tr("Off-Policy Model"))
self.on_policy_label = progress_item(tr("On-Policy Model"))
self.refresh_item = button_item(tr("Refresh Model List"), tr("REFRESH"), "", self.refresh_item = button_item(tr("Refresh Model List"), tr("REFRESH"), "",
lambda: (ui_state.params.put("ModelManager_LastSyncTime", 0), lambda: (ui_state.params.put("ModelManager_LastSyncTime", 0),
@@ -93,7 +98,8 @@ class ModelsLayout(Widget):
1, None, True, "", style.BUTTON_ACTION_WIDTH, None, True, 1, None, True, "", style.BUTTON_ACTION_WIDTH, None, True,
lambda v: f"{v / 100:.2f} m") lambda v: f"{v / 100:.2f} m")
self.items = [self.current_model_item, self.cancel_download_item, self.download_item, self.refresh_item, self.clear_cache_item, self.items = [self.current_model_item, self.cancel_download_item, self.supercombo_label, self.vision_label,
self.policy_label, self.off_policy_label, self.on_policy_label, self.refresh_item, self.clear_cache_item,
self.lane_turn_desire_toggle, self.lane_turn_value_control, self.lagd_toggle, self.delay_control, self.camera_offset] self.lane_turn_desire_toggle, self.lane_turn_value_control, self.lagd_toggle, self.delay_control, self.camera_offset]
def _update_lagd_description(self, lagd_toggle: bool): def _update_lagd_description(self, lagd_toggle: bool):
@@ -129,9 +135,14 @@ class ModelsLayout(Widget):
gui_app.push_widget(dialog) gui_app.push_widget(dialog)
def _handle_bundle_download_progress(self): def _handle_bundle_download_progress(self):
self.download_item.set_visible(False) labels = {custom.ModelManagerSP.Model.Type.supercombo: self.supercombo_label,
custom.ModelManagerSP.Model.Type.vision: self.vision_label,
custom.ModelManagerSP.Model.Type.policy: self.policy_label,
custom.ModelManagerSP.Model.Type.offPolicy: self.off_policy_label,
custom.ModelManagerSP.Model.Type.onPolicy: self.on_policy_label}
for label in labels.values():
label.set_visible(False)
self.cancel_download_item.set_visible(False) self.cancel_download_item.set_visible(False)
self._downloading = False
if not self.model_manager or (not self.model_manager.selectedBundle and not self.model_manager.activeBundle): if not self.model_manager or (not self.model_manager.selectedBundle and not self.model_manager.activeBundle):
return return
@@ -142,41 +153,32 @@ class ModelsLayout(Widget):
if not bundle: if not bundle:
return return
self.download_status = bundle.status
status_changed = self.prev_download_status != self.download_status
self.prev_download_status = self.download_status
self.cancel_download_item.set_visible(bool(self.model_manager.selectedBundle) and ui_state.params.get("ModelManager_DownloadIndex") is not None) self.cancel_download_item.set_visible(bool(self.model_manager.selectedBundle) and ui_state.params.get("ModelManager_DownloadIndex") is not None)
if (current_time := time.monotonic()) - self.last_cache_calc_time > 0.5: if (current_time := time.monotonic()) - self.last_cache_calc_time > 0.5:
self.last_cache_calc_time = current_time self.last_cache_calc_time = current_time
self.clear_cache_item.action_item.set_value(f"{self.calculate_cache_size():.2f} MB") self.clear_cache_item.action_item.set_value(f"{self.calculate_cache_size():.2f} MB")
if bundle.status == custom.ModelManagerSP.DownloadStatus.downloading: if self.download_status == custom.ModelManagerSP.DownloadStatus.downloading:
device._reset_interactive_timeout() device._reset_interactive_timeout()
# every bundle is a single chunked artifact now for model in bundle.models:
progresses = [model.artifact.downloadProgress for model in bundle.models if model.artifact.fileName] if label := labels.get(getattr(model.type, 'raw', model.type)):
if not progresses: label.set_visible(True)
return p = model.artifact.downloadProgress
text, show, color = f"pending - {bundle.displayName}", False, rl.GRAY
self.download_item.set_visible(True) if p.status == custom.ModelManagerSP.DownloadStatus.downloading:
self.download_item.action_item.update(**self._download_row_state(progresses, bundle.internalName)) text, show = f"{int(p.progress)}% - {bundle.displayName}", True
self._downloading = self.download_item.action_item.downloading elif p.status in (custom.ModelManagerSP.DownloadStatus.downloaded, custom.ModelManagerSP.DownloadStatus.cached):
status_text = tr("from cache" if p.status == custom.ModelManagerSP.DownloadStatus.cached else "downloaded")
@staticmethod text, color = f"{bundle.displayName} - {status_text if status_changed else tr('ready')}", ON_COLOR
def _download_row_state(progresses, name: str) -> dict: elif p.status == custom.ModelManagerSP.DownloadStatus.failed:
"""Maps a bundle's artifact progress to DownloadStatusAction.update kwargs.""" text, color = f"download failed - {bundle.displayName}", rl.RED
# .raw: _DynamicEnum equals its int but does not hash like it label.action_item.update(p.progress, text, show, color)
statuses = {getattr(p.status, 'raw', p.status) for p in progresses}
progress = sum(p.progress for p in progresses) / len(progresses)
ds = custom.ModelManagerSP.DownloadStatus
if ds.failed in statuses:
# close.png is authored black and a tint cannot lift it, hence close2
return {"name": name, "status_text": tr("download failed"), "text_color": rl.RED, "icon": "icons/close2.png"}
if ds.downloading in statuses:
return {"name": name, "downloading": True, "progress": progress}
if statuses <= {ds.downloaded, ds.cached}:
return {"name": name, "text_color": ON_COLOR, "icon": "icons/checkmark.png"}
# circled_slash is authored grey; tinting it again only darkens it
return {"name": name, "text_color": rl.GRAY, "icon": "icons/circled_slash.png", "icon_color": rl.WHITE}
@staticmethod @staticmethod
def _show_reset_params_dialog(): def _show_reset_params_dialog():
@@ -249,7 +251,7 @@ class ModelsLayout(Widget):
self._update_lagd_description(live_delay) self._update_lagd_description(live_delay)
self.model_manager = ui_state.sm["modelManagerSP"] self.model_manager = ui_state.sm["modelManagerSP"]
self._handle_bundle_download_progress() self._handle_bundle_download_progress()
active_name = self.model_manager.activeBundle.displayName if self.model_manager and self.model_manager.activeBundle.ref else f"{DEFAULT_MODEL} (Default)" active_name = self.model_manager.activeBundle.internalName if self.model_manager and self.model_manager.activeBundle.ref else f"{DEFAULT_MODEL} (Default)"
self.current_model_item.action_item.set_value(active_name) self.current_model_item.action_item.set_value(active_name)
if not ui_state.is_offroad(): if not ui_state.is_offroad():
@@ -4,13 +4,11 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License. This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details. See the LICENSE.md file in the root directory for more details.
""" """
from collections.abc import Callable
import pyray as rl import pyray as rl
from openpilot.cereal import custom from openpilot.cereal import custom
from openpilot.sunnypilot.models.default_model import DEFAULT_MODEL from openpilot.sunnypilot.models.default_model import DEFAULT_MODEL
from openpilot.selfdrive.ui.mici.widgets.button import BigButton from openpilot.selfdrive.ui.mici.widgets.button import BigButton
from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationDialog
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.models import ModelsLayout from openpilot.selfdrive.ui.sunnypilot.layouts.settings.models import ModelsLayout
from openpilot.selfdrive.ui.ui_state import ui_state, device from openpilot.selfdrive.ui.ui_state import ui_state, device
from openpilot.system.ui.lib.application import FontWeight, gui_app from openpilot.system.ui.lib.application import FontWeight, gui_app
@@ -19,24 +17,6 @@ from openpilot.system.ui.widgets import Widget
from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.widgets.label import UnifiedLabel
from openpilot.system.ui.widgets.scroller import NavScroller from openpilot.system.ui.widgets.scroller import NavScroller
def _build_folders() -> dict[str, list]:
manager = ui_state.sm["modelManagerSP"]
bundles = manager.availableBundles
folders = {}
for bundle in bundles:
folder = next((override.value for override in bundle.overrides if override.key == "folder"), "")
folders.setdefault(folder, []).append(bundle)
favs = ui_state.params.get("ModelManager_Favs")
favorites = set(favs.split(';')) if favs else set()
if favorites:
for fav_bundle in [bundle for bundle in bundles if bundle.ref in favorites]:
folders.setdefault("favorites", []).append(fav_bundle)
return folders
class CurrentModelInfo(Widget): class CurrentModelInfo(Widget):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@@ -66,41 +46,6 @@ class CurrentModelInfo(Widget):
self.info_text.set_position(self._rect.x + 20, self._rect.y + 161 - 25) self.info_text.set_position(self._rect.x + 20, self._rect.y + 161 - 25)
self.info_text.render() self.info_text.render()
class FolderSelectionMici(NavScroller):
def __init__(self, folder_name: str | None = None,
select_default_callback: Callable | None = None,
select_folder_callback: Callable | None = None,
select_model_callback: Callable | None = None):
super().__init__()
folders = _build_folders()
btns = []
if folder_name is None:
assert select_default_callback is not None and select_folder_callback is not None
default_btn = BigButton(f"{DEFAULT_MODEL} (Default)".lower())
default_btn.set_click_callback(select_default_callback)
btns.append(default_btn)
for folder in sorted(folders.keys(), key=lambda f: max((bundle.index for bundle in folders[f]), default=-1), reverse=True):
btn = BigButton(folder.lower())
btn.set_click_callback(lambda f=folder: select_folder_callback(f))
if folder.lower() == "favorites":
btns.insert(0, btn)
else:
btns.append(btn)
else:
assert select_model_callback is not None
for bundle in sorted(folders.get(folder_name, []), key=lambda b: b.index, reverse=True):
btn = BigButton(bundle.displayName.lower())
btn.set_click_callback(lambda b=bundle: select_model_callback(b))
btns.append(btn)
self._scroller.add_widgets(btns)
class ModelsLayoutMici(NavScroller): class ModelsLayoutMici(NavScroller):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@@ -114,47 +59,81 @@ class ModelsLayoutMici(NavScroller):
self.select_model_btn = BigButton(tr("select model")) self.select_model_btn = BigButton(tr("select model"))
self.select_model_btn.set_click_callback(self._show_folders) self.select_model_btn.set_click_callback(self._show_folders)
self.clear_cache_btn = BigButton(tr("clear cache"), "")
self.clear_cache_btn.set_click_callback(self._clear_cache)
self.cancel_download_btn = BigButton(tr("cancel download")) self.cancel_download_btn = BigButton(tr("cancel download"))
self.cancel_download_btn.set_click_callback(lambda: ui_state.params.remove("ModelManager_DownloadIndex")) self.cancel_download_btn.set_click_callback(lambda: ui_state.params.remove("ModelManager_DownloadIndex"))
self.main_items = [self.current_model_info, self.select_model_btn, self.clear_cache_btn, self.cancel_download_btn] self.main_items = [self.current_model_info, self.select_model_btn, self.cancel_download_btn]
self._scroller.add_widgets(self.main_items) self._scroller.add_widgets(self.main_items)
@property @property
def model_manager(self): def model_manager(self):
return ui_state.sm["modelManagerSP"] return ui_state.sm["modelManagerSP"]
def _get_grouped_bundles(self, favorites = None):
bundles = self.model_manager.availableBundles
folders = {}
for bundle in bundles:
folder = next((override.value for override in bundle.overrides if override.key == "folder"), "")
folders.setdefault(folder, []).append(bundle)
if favorites:
for fav_bundle in [bundle for bundle in bundles if bundle.ref in favorites]:
folders.setdefault("favorites", []).append(fav_bundle)
return folders
def _push_selection_view(self, items):
scroller = NavScroller()
scroller._scroller.add_widgets(items)
gui_app.push_widget(scroller)
def _show_folders(self): def _show_folders(self):
self.focused_widget = self.select_model_btn self.focused_widget = self.select_model_btn
def select_default(): favs = ui_state.params.get("ModelManager_Favs")
ui_state.params.remove("ModelManager_ActiveBundle") favorites = set(favs.split(';')) if favs else set()
gui_app.pop_widgets_to(self, instant=True)
self._scroller.scroll_panel.set_offset(0)
self._scroller.scroll_to(0)
def select_model(bundle): folders = self._get_grouped_bundles(favorites)
ui_state.params.put("ModelManager_DownloadIndex", bundle.index) folder_buttons = []
gui_app.pop_widgets_to(self, instant=True) default_btn = BigButton(f"{DEFAULT_MODEL} (Default)".lower())
self._scroller.scroll_panel.set_offset(0) default_btn.set_click_callback(self._select_default)
self._scroller.scroll_to(0) folder_buttons.append(default_btn)
def select_folder(folder_name): for folder in sorted(folders.keys(), key=lambda f: max((bundle.index for bundle in folders[f]), default=-1), reverse=True):
gui_app.push_widget(FolderSelectionMici(folder_name, select_model_callback=select_model)) if folder.lower() in ["release models", "master models", "favorites"]:
btn = BigButton(folder.lower())
btn.set_click_callback(lambda f=folder: self._select_folder(f))
if folder.lower() == "favorites":
folder_buttons.insert(0, btn)
else:
folder_buttons.append(btn)
self._push_selection_view(folder_buttons)
gui_app.push_widget(FolderSelectionMici(select_default_callback=select_default, select_folder_callback=select_folder)) def _pop_to_main(self):
gui_app.pop_widgets_to(self)
def _clear_cache(self): def _select_model(self, bundle):
def confirm_callback(): ui_state.params.put("ModelManager_DownloadIndex", bundle.index)
ui_state.params.put_bool("ModelManager_ClearCache", True) self._pop_to_main()
lbl = tr("slide to clear cache") def _select_default(self):
icon = gui_app.texture("icons_mici/settings/device/uninstall.png", 64, 64) ui_state.params.remove("ModelManager_ActiveBundle")
dlg = BigConfirmationDialog(lbl, icon, confirm_callback=confirm_callback, red=True) self._pop_to_main()
gui_app.push_widget(dlg)
def _select_folder(self, folder_name):
favs = ui_state.params.get("ModelManager_Favs")
favorites = set(favs.split(';')) if favs else set()
folders = self._get_grouped_bundles(favorites)
bundles = sorted(folders.get(folder_name, []), key=lambda b: b.index, reverse=True)
btns = []
for bundle in bundles:
txt = bundle.displayName.lower()
btn = BigButton(txt)
btn.set_click_callback(lambda b=bundle: self._select_model(b))
btns.append(btn)
self._push_selection_view(btns)
def hide_event(self): def hide_event(self):
super().hide_event() super().hide_event()
@@ -166,7 +145,6 @@ class ModelsLayoutMici(NavScroller):
super()._update_state() super()._update_state()
self.select_model_btn.set_enabled(ui_state.is_offroad()) self.select_model_btn.set_enabled(ui_state.is_offroad())
self.clear_cache_btn.set_enabled(ui_state.is_offroad())
self.cancel_download_btn.set_visible(False) self.cancel_download_btn.set_visible(False)
self.current_model_info.current_model_header._shimmer = False self.current_model_info.current_model_header._shimmer = False
self.current_model_info.info_header._shimmer = False self.current_model_info.info_header._shimmer = False
@@ -213,3 +191,4 @@ class ModelsLayoutMici(NavScroller):
self.current_model_info.info_header.set_text(tr("progress") + self._download_progress) self.current_model_info.info_header.set_text(tr("progress") + self._download_progress)
self.current_model_info.info_header._shimmer = True self.current_model_info.info_header._shimmer = True
self.current_model_info.info_text.set_text(f"{progress/count:.2f}%") self.current_model_info.info_text.set_text(f"{progress/count:.2f}%")
+2 -5
View File
@@ -8,10 +8,7 @@ from openpilot.common.params import Params
def get_lat_delay(params: Params, stock_lat_delay: float) -> float: def get_lat_delay(params: Params, stock_lat_delay: float) -> float:
# live learning on: use what lagd publishes.
# off: use the fixed steerActuatorDelay + software delay sum that LagdToggle caches.
if params.get_bool("LagdToggle"): if params.get_bool("LagdToggle"):
return stock_lat_delay return float(params.get("LagdValueCache", return_default=True))
return float(params.get("LagdValueCache", return_default=True)) return stock_lat_delay
@@ -117,8 +117,7 @@ def generate_queues_and_npy(input_shapes: dict, frame_skip: int, device: str = D
} }
if features_buffer: if features_buffer:
feat_q_len = frame_skip * features_buffer[1] if is_supercombo else frame_skip * (features_buffer[1] - 1) + 1 queues['feat_q'] = Tensor(np.zeros((frame_skip * (features_buffer[1] - 1) + 1, features_buffer[0], features_buffer[2]),
queues['feat_q'] = Tensor(np.zeros((feat_q_len, features_buffer[0], features_buffer[2]),
dtype=np.float32), device=device).contiguous().realize() dtype=np.float32), device=device).contiguous().realize()
queues.update({key: Tensor(value, device='NPY').realize() for key, value in npy_arrays.items() if key in ('tfm', 'big_tfm')}) queues.update({key: Tensor(value, device='NPY').realize() for key, value in npy_arrays.items() if key in ('tfm', 'big_tfm')})
+2 -2
View File
@@ -140,8 +140,8 @@ class ModelCache:
class ModelFetcher: class ModelFetcher:
"""Handles fetching and caching of model data from remote source""" """Handles fetching and caching of model data from remote source"""
MODEL_URL = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-models/refs/heads/gh-pages/docs/driving_models_v20.json" MODEL_URL = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-models/refs/heads/gh-pages/docs/driving_models_v19.json"
MODEL_URL_USBGPU = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-models/refs/heads/gh-pages/docs/driving_models_usbgpu_v20.json" MODEL_URL_USBGPU = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-models/refs/heads/gh-pages/docs/driving_models_usbgpu_v19.json"
def __init__(self, params: Params): def __init__(self, params: Params):
self.params = params self.params = params
+2 -2
View File
@@ -16,7 +16,6 @@ from openpilot.common.utils import strip_deprecated_keys
from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.common.params import Params from openpilot.common.params import Params
from openpilot.common.realtime import DT_HW from openpilot.common.realtime import DT_HW
from openpilot.selfdrive.modeld.helpers import MODELS_DIR, usbgpu_compiled
from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert
from openpilot.common.hardware import HARDWARE, COMMA_HARDWARE from openpilot.common.hardware import HARDWARE, COMMA_HARDWARE
from openpilot.common.basedir import BASEDIR from openpilot.common.basedir import BASEDIR
@@ -239,7 +238,8 @@ def hardware_thread(end_event, hw_queue) -> None:
fan_controller = FanController(int(1./DT_HW)) fan_controller = FanController(int(1./DT_HW))
chestnut = Chestnut() chestnut = Chestnut()
big_model_available = (MODELS_DIR / 'big_driving_supercombo.onnx').is_file() or usbgpu_compiled() big_model_available = os.path.isfile(os.path.join(BASEDIR, "openpilot/selfdrive/modeld/models/big_driving_supercombo.onnx")) or \
os.path.isfile(os.path.join(BASEDIR, "openpilot/selfdrive/modeld/models/big_driving_tinygrad.pkl.chunkmanifest"))
while not end_event.is_set(): while not end_event.is_set():
sm.update(PANDA_STATES_TIMEOUT) sm.update(PANDA_STATES_TIMEOUT)
@@ -4,15 +4,7 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License. This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details. See the LICENSE.md file in the root directory for more details.
""" """
from collections.abc import Callable
import pyray as rl
from openpilot.system.ui.lib.application import FontWeight
from openpilot.system.ui.sunnypilot.lib.styles import style
from openpilot.system.ui.sunnypilot.widgets.list_view import ButtonActionSP from openpilot.system.ui.sunnypilot.widgets.list_view import ButtonActionSP
from openpilot.system.ui.widgets.label import UnifiedLabel
from openpilot.system.ui.widgets.list_view import BUTTON_WIDTH, BUTTON_HEIGHT, TEXT_PADDING, _resolve_value
class NoElideButtonAction(ButtonActionSP): class NoElideButtonAction(ButtonActionSP):
@@ -20,38 +12,6 @@ class NoElideButtonAction(ButtonActionSP):
return super().get_width_hint() + 1 return super().get_width_hint() + 1
class ScrollingButtonAction(ButtonActionSP):
"""ButtonActionSP whose value scrolls instead of eliding when it doesn't fit."""
def __init__(self, text: str | Callable[[], str], width: int = style.BUTTON_ACTION_WIDTH,
enabled: bool | Callable[[], bool] = True):
super().__init__(text=text, width=width, enabled=enabled)
self._value_label = UnifiedLabel("", font_size=style.ITEM_TEXT_FONT_SIZE, font_weight=FontWeight.NORMAL,
text_color=self._value_color, scroll=True,
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE)
def set_value(self, value: str | Callable[[], str], color: rl.Color = style.ITEM_TEXT_VALUE_COLOR):
if self.value != _resolve_value(value, ""):
self._value_label.reset_scroll()
super().set_value(value, color)
self._value_label.set_text(value)
self._value_label.set_text_color(color)
def _render(self, rect: rl.Rectangle) -> bool:
"""Duplicate of ButtonActionSP._render, with the value drawn by a scrolling label"""
self._button.set_text(self.text)
self._button.set_enabled(_resolve_value(self.enabled))
button_rect = rl.Rectangle(rect.x + rect.width - BUTTON_WIDTH, rect.y + (rect.height - BUTTON_HEIGHT) / 2, BUTTON_WIDTH, BUTTON_HEIGHT)
self._button.render(button_rect)
if self.value:
self._value_label.render(rl.Rectangle(rect.x, rect.y, rect.width - BUTTON_WIDTH - TEXT_PADDING, rect.height))
pressed = self._pressed
self._pressed = False
return pressed
class AlertFadeAnimator: class AlertFadeAnimator:
def __init__(self, target_fps: int, duration_on: float = 0.75, rc: float = 0.05): def __init__(self, target_fps: int, duration_on: float = 0.75, rc: float = 0.05):
from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.filter_simple import FirstOrderFilter
@@ -1,166 +0,0 @@
"""
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
import math
import numpy as np
import pyray as rl
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.lib.shader_polygon import draw_polygon, Gradient
from openpilot.system.ui.lib.text_measure import measure_text_cached
from openpilot.system.ui.sunnypilot.lib.styles import style
from openpilot.system.ui.sunnypilot.widgets.list_view import ListItemSP
from openpilot.system.ui.widgets.label import UnifiedLabel
from openpilot.system.ui.widgets.list_view import ItemAction
FONT_SIZE = style.ITEM_TEXT_FONT_SIZE
ICON_SIZE = 56
ICON_PADDING = 12
BAR_WIDTH = 1100
BAR_HEIGHT = 20
BAR_GAP = 16
BAR_RADIUS = BAR_HEIGHT / 2
CAPSULE_POINTS = 24
RAIL_COLOR = rl.Color(60, 60, 60, 255)
FILL_COLOR = rl.Color(30, 121, 232, 255)
# rl.WHITE is a tuple; the shimmer path reads .a off the color
TEXT_COLOR = rl.Color(255, 255, 255, 255)
SWEEP_SPEED = 550.0 # px/s
SWEEP_BAND = 240.0 # highlight half-width, px
SWEEP_DIM = 0.65
class DownloadStatusAction(ItemAction):
"""Model download row: a name + percent over a progress rail while downloading, a name + icon otherwise."""
def __init__(self):
super().__init__(width=BAR_WIDTH)
self.name = ""
self.status_text = ""
self.downloading = False
self.text_color = rl.GRAY
self.icon: str | None = None
self.icon_color: rl.Color | None = None
self._font = gui_app.font(FontWeight.NORMAL)
# raw progress arrives in steps, one per 128KB chunk the manager publishes
self._progress = FirstOrderFilter(0.0, 0.5, 1 / gui_app.target_fps)
# integrated per frame; (t * speed) % span jumps whenever the fill width changes
self._sweep = 0.0
self._name_label = UnifiedLabel("", font_size=FONT_SIZE, font_weight=FontWeight.NORMAL, text_color=TEXT_COLOR,
alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT,
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE)
self._percent_label = UnifiedLabel("", font_size=FONT_SIZE, font_weight=FontWeight.NORMAL, text_color=TEXT_COLOR,
alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT,
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE)
def update(self, name, downloading=False, progress=0.0, status_text="", text_color=rl.GRAY, icon=None, icon_color=None):
if downloading and not self.downloading:
self._name_label.reset_shimmer()
self._progress.x = progress
self._sweep = 0.0
self.name = name
self.downloading = downloading
self.status_text = status_text
self.text_color = text_color
self.icon = icon
self.icon_color = icon_color
self._name_label._shimmer = downloading
if downloading:
self._progress.update(progress)
self._sweep += SWEEP_SPEED / gui_app.target_fps
@property
def _idle_text(self) -> str:
return f"{self.name} - {self.status_text}" if self.status_text else self.name
def get_width_hint(self) -> float:
if self.downloading:
return BAR_WIDTH
width = measure_text_cached(self._font, self._idle_text, FONT_SIZE).x
if self.icon:
width += ICON_SIZE + ICON_PADDING
return width
def _render(self, rect: rl.Rectangle):
if self.downloading:
self._render_downloading(rect)
else:
self._render_idle(rect)
def _sweep_gradient(self, width: float) -> Gradient:
# clearance at both ends keeps the wrap offscreen
center = (self._sweep % (width + 2 * SWEEP_BAND)) - SWEEP_BAND
def band(x: float) -> float:
return max(0.0, 1.0 - abs(x - center) / SWEEP_BAND)
# sampling the corners is exact for a piecewise linear band
xs = sorted({0.0, width} | {min(max(center + o, 0.0), width) for o in (-SWEEP_BAND, 0.0, SWEEP_BAND)}, reverse=True)
# the gradient axis runs right-to-left in screen space
stops = [1.0 - x / width for x in xs]
# alpha here is the lift over the SWEEP_DIM base, not the final opacity
colors = [rl.Color(FILL_COLOR.r, FILL_COLOR.g, FILL_COLOR.b, int(255 * band(x))) for x in xs]
return Gradient(start=(0.0, 0.0), end=(1.0, 0.0), colors=colors, stops=stops)
@staticmethod
def _capsule(rect: rl.Rectangle) -> np.ndarray:
"""Rounded-end ribbon so the gradient covers the caps."""
r = rect.height / 2
cy = rect.y + r
top, bottom = [], []
for i in range(CAPSULE_POINTS):
x = rect.x + rect.width * i / (CAPSULE_POINTS - 1)
d = min(x - rect.x, rect.x + rect.width - x, r)
h = math.sqrt(max(r * r - (r - d) ** 2, 0.0))
top.append((x, cy - h))
bottom.append((x, cy + h))
return np.array(top + bottom[::-1], dtype=np.float32)
def _draw_fill(self, rail: rl.Rectangle, fill_width: float):
if fill_width <= 0:
return
fill = rl.Rectangle(rail.x, rail.y, fill_width, rail.height)
rl.draw_rectangle_rounded(fill, 1.0, 10, rl.Color(FILL_COLOR.r, FILL_COLOR.g, FILL_COLOR.b, int(255 * SWEEP_DIM)))
draw_polygon(fill, self._capsule(fill), gradient=self._sweep_gradient(fill_width))
def _render_downloading(self, rect: rl.Rectangle):
percent = f"{int(self._progress.x)}%"
text_height = measure_text_cached(self._font, percent, FONT_SIZE).y
top = rect.y + (rect.height - (text_height + BAR_GAP + BAR_HEIGHT)) / 2
text_rect = rl.Rectangle(rect.x, top, rect.width, text_height)
self._name_label.set_text(self.name)
self._name_label.render(text_rect)
self._percent_label.set_text(percent)
self._percent_label.render(text_rect)
rail = rl.Rectangle(rect.x, top + text_height + BAR_GAP, rect.width, BAR_HEIGHT)
rl.draw_rectangle_rounded(rail, 1.0, 10, RAIL_COLOR)
self._draw_fill(rail, max(0.0, min(rect.width, rect.width * (self._progress.x / 100.0))))
def _render_idle(self, rect: rl.Rectangle):
text = self._idle_text
text_size = measure_text_cached(self._font, text, FONT_SIZE)
right = rect.x + rect.width
if self.icon:
texture = gui_app.texture(self.icon, ICON_SIZE, ICON_SIZE, keep_aspect_ratio=True)
rl.draw_texture_v(texture, rl.Vector2(right - texture.width, rect.y + (rect.height - texture.height) / 2),
self.icon_color or self.text_color)
right -= texture.width + ICON_PADDING
rl.draw_text_ex(self._font, text, rl.Vector2(right - text_size.x, rect.y + (rect.height - text_size.y) / 2),
FONT_SIZE, 0, self.text_color)
def download_status_item(title):
return ListItemSP(title=title, action_item=DownloadStatusAction(), title_color=style.ITEM_TEXT_COLOR)
@@ -7,7 +7,7 @@ from openpilot.common.test import OpenpilotTestCase
from openpilot.cereal import messaging, log from openpilot.cereal import messaging, log
from teleoprtc.tracks import VIDEO_CLOCK_RATE from teleoprtc.tracks import VIDEO_CLOCK_RATE
from openpilot.system.webrtc.webrtcd import CerealOutgoingMessageProxy, CerealIncomingMessageProxy, ServerState, handle_get_stream from openpilot.system.webrtc.webrtcd import CerealOutgoingMessageProxy, CerealIncomingMessageProxy
from openpilot.system.webrtc.device.video import LiveStreamVideoStreamTrack from openpilot.system.webrtc.device.video import LiveStreamVideoStreamTrack
@@ -80,8 +80,3 @@ class TestStreamSession(OpenpilotTestCase):
start_pts = packet.pts start_pts = packet.pts
assert abs(i + packet.pts - (start_pts + (((time.monotonic_ns() - start_ns) * VIDEO_CLOCK_RATE) // 1_000_000_000))) < 450 #5ms assert abs(i + packet.pts - (start_pts + (((time.monotonic_ns() - start_ns) * VIDEO_CLOCK_RATE) // 1_000_000_000))) < 450 #5ms
assert bytes(packet) == b"" assert bytes(packet) == b""
def test_stream_rejects_non_json_content_type(self):
response = self.loop.run_until_complete(handle_get_stream(ServerState(), b"{}", "text/plain"))
assert response == (415, b'{"error": "unsupported media type"}', "application/json; charset=utf-8")
+3 -6
View File
@@ -395,10 +395,7 @@ def _text_response(text: str, status: int = 200) -> tuple[int, bytes, str]:
return (status, text.encode(), "text/plain; charset=utf-8") return (status, text.encode(), "text/plain; charset=utf-8")
async def handle_get_stream(state: ServerState, raw_body: bytes, content_type: str) -> tuple[int, bytes, str]: async def handle_get_stream(state: ServerState, raw_body: bytes) -> tuple[int, bytes, str]:
if content_type != "application/json":
return _json_response({"error": "unsupported media type"}, status=415)
stream_dict = state.streams stream_dict = state.streams
body = StreamRequestBody(**json.loads(raw_body)) body = StreamRequestBody(**json.loads(raw_body))
@@ -511,7 +508,7 @@ class WebrtcdHandler(BaseHTTPRequestHandler):
services = parse_qs(parsed.query).get("services", [""])[0] services = parse_qs(parsed.query).get("services", [""])[0]
result = self._run(handle_get_schema(self.server.state, services)) result = self._run(handle_get_schema(self.server.state, services))
elif parsed.path == "/stream": elif parsed.path == "/stream":
result = self._run(handle_get_stream(self.server.state, self._read_body(), self.headers.get_content_type())) result = self._run(handle_get_stream(self.server.state, self._read_body()))
else: # /notify else: # /notify
try: try:
payload = json.loads(self._read_body()) payload = json.loads(self._read_body())
@@ -614,7 +611,7 @@ def webrtcd_thread(host: str, port: int):
def main(): def main():
parser = argparse.ArgumentParser(description="WebRTC daemon") parser = argparse.ArgumentParser(description="WebRTC daemon")
parser.add_argument("--host", type=str, default="127.0.0.1", help="Host to listen on") parser.add_argument("--host", type=str, default="0.0.0.0", help="Host to listen on")
parser.add_argument("--port", type=int, default=5001, help="Port to listen on") parser.add_argument("--port", type=int, default=5001, help="Port to listen on")
args = parser.parse_args() args = parser.parse_args()
+1 -1
View File
@@ -21,7 +21,7 @@ dependencies = [
"tqdm", # cars (fw_versions.py) on start + many one-off uses "tqdm", # cars (fw_versions.py) on start + many one-off uses
# core # core
"scons==4.10.1", # 4.11 removed the qt3 tool still used to build Cabana "scons",
"pycapnp==2.1.0", # 2.2 introduces a memory leak due to cyclic references "pycapnp==2.1.0", # 2.2 introduces a memory leak due to cyclic references
"numpy >=2.0", "numpy >=2.0",
Generated
+61 -61
View File
@@ -381,20 +381,20 @@ wheels = [
[[package]] [[package]]
name = "deepmerge" name = "deepmerge"
version = "3.0" version = "2.1.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b7/6c/9f4577a36d5f463a3a3f8322bd65d33e1a1a6b6ba1d692a5ebc3cba19015/deepmerge-3.0.tar.gz", hash = "sha256:14ed69f063de64b7743985c732ccff5d6c34ff4560946e7fbfd99086b853b9ce", size = 22279, upload-time = "2026-08-17T05:50:53.161Z" } sdist = { url = "https://files.pythonhosted.org/packages/2a/78/6e9e20106224083cfb817d2d3c26e80e72258d617b616721a169b87081e0/deepmerge-2.1.0.tar.gz", hash = "sha256:07ca7a7b8935df596c512fa8161877c0487ac61f691c07766e7d71d2b23bdd2f", size = 21449, upload-time = "2026-06-22T05:46:07.669Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/a8/d7/7f19bedd30b90b72865aeec3a29127bed6dee6c9ef0324bb5b4d424bb0e3/deepmerge-3.0-py3-none-any.whl", hash = "sha256:c8541c3e186dc88d19a5513ad3a0b2d0b22beaa780969fc0c13b995a64265365", size = 14855, upload-time = "2026-08-17T05:50:52.218Z" }, { url = "https://files.pythonhosted.org/packages/51/25/2a75b47cb057b1e164c604fb81ab690a6cdb5e2260ce651194eae90f64a3/deepmerge-2.1.0-py3-none-any.whl", hash = "sha256:8f148339a91d680a75ecb74ade235d9e759a93df373a0b04e9d31c8666cfeb75", size = 14345, upload-time = "2026-06-22T05:46:06.742Z" },
] ]
[[package]] [[package]]
name = "filelock" name = "filelock"
version = "3.32.3" version = "3.32.2"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/7d/64/a02e6765de08964ed371eca577870593245afc9dfac16d037de7c10d18e6/filelock-3.32.3.tar.gz", hash = "sha256:0ffa185a3540854c95caa7fa76b76cb219d907415e2c5dc9af25fd970563487f", size = 218135, upload-time = "2026-08-13T16:00:05.577Z" } sdist = { url = "https://files.pythonhosted.org/packages/f6/57/3ba6e6cb097f85b855b00163d169f35365f44277df044dcf96d55b8f62a3/filelock-3.32.2.tar.gz", hash = "sha256:c33351e1f49cae33414acbc6d56784e6ecee82514ec90795da1161fc4836b5b8", size = 217172, upload-time = "2026-07-29T22:46:04.895Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/a7/8e/50f46a9c0ce8d2861a394c1347caae037ea0431d2f67d7feb151cbc4649a/filelock-3.32.3-py3-none-any.whl", hash = "sha256:7f0ca4bcc0e181c60dbbd8aa9ab5b120ebb99e4e064e83636340056f833a1f09", size = 98901, upload-time = "2026-08-13T16:00:03.974Z" }, { url = "https://files.pythonhosted.org/packages/c1/e8/72f8cef9fdfeffe06213fe8508039396ee48daa0e3259457ed766173bfd6/filelock-3.32.2-py3-none-any.whl", hash = "sha256:87dd94cf281e586d135fa51132b8e3d9a598b316e90377a288663c9321036c82", size = 98830, upload-time = "2026-07-29T22:46:03.52Z" },
] ]
[[package]] [[package]]
@@ -478,7 +478,7 @@ wheels = [
[[package]] [[package]]
name = "huggingface-hub" name = "huggingface-hub"
version = "1.28.0" version = "1.27.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "click" }, { name = "click" },
@@ -491,18 +491,18 @@ dependencies = [
{ name = "tqdm" }, { name = "tqdm" },
{ name = "typing-extensions" }, { name = "typing-extensions" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/c6/ae/222a91937ebee7f62c0ca8f5ee0afd97577caf24c0abb927d1f5c7e9f6d2/huggingface_hub-1.28.0.tar.gz", hash = "sha256:46a2e950c09234de54093d587d1675382f0d08dbd600d9fb599b5932f5b2c6cb", size = 959609, upload-time = "2026-08-18T12:27:15.101Z" } sdist = { url = "https://files.pythonhosted.org/packages/3e/9b/ddf3d02a8681f1b9ce52fda03d755dad6b74c4f8172304c4c8d2975450f9/huggingface_hub-1.27.0.tar.gz", hash = "sha256:c1fed40ea82a6b41b477f5243546549b792ae0a93abcea608cff66089bf8f8df", size = 942668, upload-time = "2026-08-07T12:48:05.161Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/51/0e/eafef18f1a75e125e68395db21131db0cf868a128ecd2fce69b4df6c584b/huggingface_hub-1.28.0-py3-none-any.whl", hash = "sha256:58a8bacb03072edfc38067065e9dc24bbb34805410fcd36a1632de0b329660bb", size = 793202, upload-time = "2026-08-18T12:27:12.719Z" }, { url = "https://files.pythonhosted.org/packages/de/d8/95b735e183957c1f26d94c52977f09d466d55119cbbc1558ea4975e4c216/huggingface_hub-1.27.0-py3-none-any.whl", hash = "sha256:7df6827c2f956c60fbaa64646e979e566db76f619dd0a9729dfb8c5a3eb4f68d", size = 784926, upload-time = "2026-08-07T12:48:02.905Z" },
] ]
[[package]] [[package]]
name = "idna" name = "idna"
version = "3.19" version = "3.18"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/5f/f7/abb373e5757eaec4b922b92f97ec8d6d7e057cf06778247604fbc4e7c3f3/idna-3.19.tar.gz", hash = "sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15", size = 215237, upload-time = "2026-08-18T05:14:24.27Z" } sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" },
] ]
[[package]] [[package]]
@@ -847,7 +847,7 @@ requires-dist = [
{ name = "rednose", marker = "extra == 'submodules'", editable = "rednose_repo" }, { name = "rednose", marker = "extra == 'submodules'", editable = "rednose_repo" },
{ name = "requests" }, { name = "requests" },
{ name = "ruff", marker = "extra == 'testing'" }, { name = "ruff", marker = "extra == 'testing'" },
{ name = "scons", specifier = "==4.10.1" }, { name = "scons" },
{ name = "sentry-sdk" }, { name = "sentry-sdk" },
{ name = "setproctitle" }, { name = "setproctitle" },
{ name = "sounddevice" }, { name = "sounddevice" },
@@ -971,11 +971,11 @@ wheels = [
[[package]] [[package]]
name = "pygments" name = "pygments"
version = "2.21.0" version = "2.20.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" } sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" }, { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
] ]
[[package]] [[package]]
@@ -1136,11 +1136,11 @@ wheels = [
[[package]] [[package]]
name = "scons" name = "scons"
version = "4.10.1" version = "4.11.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/7d/c9/2f430bb39e4eccba32ce8008df4a3206df651276422204e177a09e12b30b/scons-4.10.1.tar.gz", hash = "sha256:99c0e94a42a2c1182fa6859b0be697953db07ba936ecc9817ae0d218ced20b15", size = 3258403, upload-time = "2025-11-16T22:43:39.258Z" } sdist = { url = "https://files.pythonhosted.org/packages/dd/82/3c4e089ac8df2eaee8a7f14e489b2a76f94f4c1d8defa4e46c8ad15cae86/scons-4.11.0.tar.gz", hash = "sha256:5ba48f9e2eb6b9178cabdc9893792418e6970c84f43f4b027e4468e20616a89c", size = 3269126, upload-time = "2026-08-11T04:29:45.62Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/ce/bf/931fb9fbb87234c32b8b1b1c15fba23472a10777c12043336675633809a7/scons-4.10.1-py3-none-any.whl", hash = "sha256:bd9d1c52f908d874eba92a8c0c0a8dcf2ed9f3b88ab956d0fce1da479c4e7126", size = 4136069, upload-time = "2025-11-16T22:43:35.933Z" }, { url = "https://files.pythonhosted.org/packages/fc/ac/a4445bbbd58a5fa6a5c8b3b0458ffbee04e4acaff87677058eab9c6af682/scons-4.11.0-py3-none-any.whl", hash = "sha256:2edc077aaeafc43377ba46ce1fa3e7b40edea59c62db9ef7e39e07dc88b754fa", size = 4123742, upload-time = "2026-08-11T04:29:42.881Z" },
] ]
[[package]] [[package]]
@@ -1194,18 +1194,18 @@ wheels = [
[[package]] [[package]]
name = "sounddevice" name = "sounddevice"
version = "0.5.6" version = "0.5.5"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "cffi" }, { name = "cffi" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/ec/db/0c890e2d9aab9ba284021efc02e1d3aebfecab1b611762d7434602209bcf/sounddevice-0.5.6.tar.gz", hash = "sha256:8ec9fbfde2e32f020b167e348f3ab3bac6625a5f15af524d790108ac7147a410", size = 1120094, upload-time = "2026-08-17T07:55:05.048Z" } sdist = { url = "https://files.pythonhosted.org/packages/2a/f9/2592608737553638fca98e21e54bfec40bf577bb98a61b2770c912aab25e/sounddevice-0.5.5.tar.gz", hash = "sha256:22487b65198cb5bf2208755105b524f78ad173e5ab6b445bdab1c989f6698df3", size = 143191, upload-time = "2026-01-23T18:36:43.529Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/72/1f/62eef605172bddc1017508469a12f75bc7c4194ece35c734f822795f53b1/sounddevice-0.5.6-py3-none-any.whl", hash = "sha256:de099612311ad81e55d31ccbd83f43ea6bf4d87b48f9b6ea55a1fbcde0eee4e0", size = 32793, upload-time = "2026-08-17T07:54:57.507Z" }, { url = "https://files.pythonhosted.org/packages/1e/0a/478e441fd049002cf308520c0d62dd8333e7c6cc8d997f0dda07b9fbcc46/sounddevice-0.5.5-py3-none-any.whl", hash = "sha256:30ff99f6c107f49d25ad16a45cacd8d91c25a1bcdd3e81a206b921a3a6405b1f", size = 32807, upload-time = "2026-01-23T18:36:35.649Z" },
{ url = "https://files.pythonhosted.org/packages/b6/84/85e719d49cf98b2f406d9ac9c338892286c4448eb42ef0b2625ccf159616/sounddevice-0.5.6-py3-none-macosx_10_6_x86_64.macosx_10_6_universal2.whl", hash = "sha256:e3aef00ad8b1d1740eb66d9a7671eab88a4d2b8fa4ab33498d742e63b65c309c", size = 1009647, upload-time = "2026-08-17T07:54:58.814Z" }, { url = "https://files.pythonhosted.org/packages/56/f9/c037c35f6d0b6bc3bc7bfb314f1d6f1f9a341328ef47cd63fc4f850a7b27/sounddevice-0.5.5-py3-none-macosx_10_6_x86_64.macosx_10_6_universal2.whl", hash = "sha256:05eb9fd6c54c38d67741441c19164c0dae8ce80453af2d8c4ad2e7823d15b722", size = 108557, upload-time = "2026-01-23T18:36:37.41Z" },
{ url = "https://files.pythonhosted.org/packages/c5/6f/6292145099f72a153a710245f46ae43e5fb6c77bec1b6086cb76c12dc280/sounddevice-0.5.6-py3-none-win32.whl", hash = "sha256:b36b807eb02abd257198bf84b2af05e4fea199a9d2f0019014169c7136d45e9c", size = 1009627, upload-time = "2026-08-17T07:55:00.401Z" }, { url = "https://files.pythonhosted.org/packages/88/a1/d19dd9889cd4bce2e233c4fac007cd8daaf5b9fe6e6a5d432cf17be0b807/sounddevice-0.5.5-py3-none-win32.whl", hash = "sha256:1234cc9b4c9df97b6cbe748146ae0ec64dd7d6e44739e8e42eaa5b595313a103", size = 317765, upload-time = "2026-01-23T18:36:39.047Z" },
{ url = "https://files.pythonhosted.org/packages/8d/3e/cbc593c31a5f0d817b3fe97e64aa8461bd0f55cb07b67ce1b776296ae336/sounddevice-0.5.6-py3-none-win_amd64.whl", hash = "sha256:7f4162f514f007b0bf25a3ccfed3f1705bc2ec311888a90232729eec4f57a4f4", size = 1009630, upload-time = "2026-08-17T07:55:02.088Z" }, { url = "https://files.pythonhosted.org/packages/c3/0e/002ed7c4c1c2ab69031f78989d3b789fee3a7fba9e586eb2b81688bf4961/sounddevice-0.5.5-py3-none-win_amd64.whl", hash = "sha256:cfc6b2c49fb7f555591c78cb8ecf48d6a637fd5b6e1db5fec6ed9365d64b3519", size = 365324, upload-time = "2026-01-23T18:36:40.496Z" },
{ url = "https://files.pythonhosted.org/packages/60/a4/b0c21c9f215a6fd9606b8f8748c21212dc098e5d5a2d93068c50edcf19b4/sounddevice-0.5.6-py3-none-win_arm64.whl", hash = "sha256:c8ae19173e5f27f8c12d4b5eee2dbfe542cee125d591e663e0fb4dfb75246d45", size = 1009630, upload-time = "2026-08-17T07:55:03.689Z" }, { url = "https://files.pythonhosted.org/packages/4e/39/a61d4b83a7746b70d23d9173be688c0c6bfc7173772344b7442c2c155497/sounddevice-0.5.5-py3-none-win_arm64.whl", hash = "sha256:3861901ddd8230d2e0e8ae62ac320cdd4c688d81df89da036dcb812f757bb3e6", size = 317115, upload-time = "2026-01-23T18:36:42.235Z" },
] ]
[[package]] [[package]]
@@ -1285,6 +1285,7 @@ requires-dist = [
{ name = "pylint", marker = "extra == 'linting'" }, { name = "pylint", marker = "extra == 'linting'" },
{ name = "pytest", marker = "extra == 'testing-minimal'" }, { name = "pytest", marker = "extra == 'testing-minimal'" },
{ name = "pytest-split", marker = "extra == 'testing-minimal'" }, { name = "pytest-split", marker = "extra == 'testing-minimal'" },
{ name = "pytest-timeout", marker = "extra == 'testing-minimal'" },
{ name = "pytest-xdist", marker = "extra == 'testing-minimal'" }, { name = "pytest-xdist", marker = "extra == 'testing-minimal'" },
{ name = "ruff", marker = "extra == 'linting'", specifier = "==0.14.10" }, { name = "ruff", marker = "extra == 'linting'", specifier = "==0.14.10" },
{ name = "safetensors", marker = "extra == 'testing-unit'" }, { name = "safetensors", marker = "extra == 'testing-unit'" },
@@ -1293,7 +1294,6 @@ requires-dist = [
{ name = "tiktoken", marker = "extra == 'testing'" }, { name = "tiktoken", marker = "extra == 'testing'" },
{ name = "tinygrad", extras = ["testing-minimal"], marker = "extra == 'testing-unit'" }, { name = "tinygrad", extras = ["testing-minimal"], marker = "extra == 'testing-unit'" },
{ name = "tinygrad", extras = ["testing-unit"], marker = "extra == 'testing'" }, { name = "tinygrad", extras = ["testing-unit"], marker = "extra == 'testing'" },
{ name = "tinymesa", marker = "extra == 'mesa'", specifier = "==25.2.7.2" },
{ name = "torch", marker = "extra == 'testing-minimal'", specifier = "==2.9.1" }, { name = "torch", marker = "extra == 'testing-minimal'", specifier = "==2.9.1" },
{ name = "tqdm", marker = "extra == 'testing-unit'" }, { name = "tqdm", marker = "extra == 'testing-unit'" },
{ name = "transformers", marker = "extra == 'testing'" }, { name = "transformers", marker = "extra == 'testing'" },
@@ -1301,7 +1301,7 @@ requires-dist = [
{ name = "typing-extensions", marker = "extra == 'linting'" }, { name = "typing-extensions", marker = "extra == 'linting'" },
{ name = "z3-solver", marker = "extra == 'testing-minimal'", specifier = "<4.15.4" }, { name = "z3-solver", marker = "extra == 'testing-minimal'", specifier = "<4.15.4" },
] ]
provides-extras = ["linting", "testing-minimal", "testing-unit", "testing", "docs", "mesa"] provides-extras = ["linting", "testing-minimal", "testing-unit", "testing", "docs"]
[[package]] [[package]]
name = "tomli" name = "tomli"
@@ -1335,27 +1335,27 @@ wheels = [
[[package]] [[package]]
name = "ty" name = "ty"
version = "0.0.73" version = "0.0.72"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e5/90/c4e1bb4cead3b644c3e258a27f9b05c7dc5eb0ec96a4f5282194edae9e0d/ty-0.0.73.tar.gz", hash = "sha256:823d4ce0d237bfc7eb6bcee70842f2c0706113813a16951077840743712f4b74", size = 6712739, upload-time = "2026-08-19T03:12:43.381Z" } sdist = { url = "https://files.pythonhosted.org/packages/d5/df/656e684bafb13c1d146e7d5b5f3e7978ca177232acc84998ff36427e9462/ty-0.0.72.tar.gz", hash = "sha256:ec2b8066b618df18cab4cb8e992f8da45d360332acb23fa34df7fa29cd1b9d3a", size = 6654939, upload-time = "2026-08-14T21:35:42.612Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/e4/0f/f5e1801e55cc631f2db193276675b30561b963a2403da832bffb5d100267/ty-0.0.73-py3-none-linux_armv6l.whl", hash = "sha256:90a946082bf9bc446b5e72973d9f4ff1222a240b2ca4c9e6eed61eb913e30810", size = 12715452, upload-time = "2026-08-19T03:12:06.673Z" }, { url = "https://files.pythonhosted.org/packages/e2/3b/f51461239a4e66565d4b362f97a3b55fe7fdba2e944068341f87c62f6743/ty-0.0.72-py3-none-linux_armv6l.whl", hash = "sha256:fda86db153ffd85ee52000cf175d6a3f1c0223772cf7c5b6f726200bf92c7b44", size = 12621989, upload-time = "2026-08-14T21:35:01.676Z" },
{ url = "https://files.pythonhosted.org/packages/54/32/515dd05074c213b433524ab97eb003b0132ae7e358e0d75633ba7a314ed8/ty-0.0.73-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b7d6b5c6a6db7ea95fbbc16af514ef44a27a29a2fe1dc798900790364d170209", size = 12301870, upload-time = "2026-08-19T03:12:08.924Z" }, { url = "https://files.pythonhosted.org/packages/ca/fb/79ddf683affc679ca856f3510b5640ec3a88a842ba5f654f5d4bc78f1786/ty-0.0.72-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ceb944c612529b9023acfdc9cf4c0dcbb722549f9d17d46baecd1141baf01d7f", size = 12233910, upload-time = "2026-08-14T21:35:04.334Z" },
{ url = "https://files.pythonhosted.org/packages/50/4d/085b4889f0d4bbe4af8b96242d4a1cb209fff95967cfa239ea141983719b/ty-0.0.73-py3-none-macosx_11_0_arm64.whl", hash = "sha256:dd6f657f463e01372d8688f235be164750c8db722c97da27fa4903aa8d40b203", size = 12111741, upload-time = "2026-08-19T03:12:11.067Z" }, { url = "https://files.pythonhosted.org/packages/5d/45/10562a0d84802158db8fa4ec46de54aa9fdcecdeeaabbfe3639ae7042b66/ty-0.0.72-py3-none-macosx_11_0_arm64.whl", hash = "sha256:108d76218333d6c092e5f1cebf8e9b06f25738613a0236a28e2dd47c936ee52c", size = 12084108, upload-time = "2026-08-14T21:35:06.686Z" },
{ url = "https://files.pythonhosted.org/packages/95/f6/d6ec277cadfecf03ad4c18551b67c4c6eb7807a0560d801db14be99d7a89/ty-0.0.73-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc2de468e33fd44c9ff1c43473a7316f4289480f5cba8995a67b6d22aee39ca9", size = 12196124, upload-time = "2026-08-19T03:12:13.14Z" }, { url = "https://files.pythonhosted.org/packages/a1/dc/1fe1aef8d697e3509face271a5331700c7aa1d1e44a4b622707bdfa41d4b/ty-0.0.72-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f3943f186f741a2499a31053872169250c9264a9a49684920e48d8fcf4ef4f5", size = 12132640, upload-time = "2026-08-14T21:35:09.305Z" },
{ url = "https://files.pythonhosted.org/packages/75/b7/ce78d8707563af9cae9bbd25328bfbc4931035085bd20089adf0c418f70e/ty-0.0.73-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2942fa0ef795a66034cdc8d75a72f453442f3b58ff2f69b4da05b7b954765b55", size = 12488557, upload-time = "2026-08-19T03:12:15.252Z" }, { url = "https://files.pythonhosted.org/packages/14/46/41ceb265e96969487311a2014bd0e53abb4fbc1395efb2ebe411fcb4db62/ty-0.0.72-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cf283c07dc3cc52ca48a3ad8ab100fb5aec3aebbd03ef6a12d5f910b8e596fc5", size = 12402489, upload-time = "2026-08-14T21:35:11.555Z" },
{ url = "https://files.pythonhosted.org/packages/d8/e8/329b9851b23502758c5c98e8cc875ea2a1b4c9674b4ca3a86da56a5063d3/ty-0.0.73-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e0f1ef14f642e18ac4e7a616a2796dcf7a5d82e28cd17f9796494acc7c4aabb", size = 13215606, upload-time = "2026-08-19T03:12:17.225Z" }, { url = "https://files.pythonhosted.org/packages/2b/45/30bf43cb4fd505c5c2dd30fda27dde5f05208686cd21217adec77c954204/ty-0.0.72-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:95f3b6462c38f9f115d10cee21f47fedf715fcf2040daf36eef210359300bc7c", size = 13130835, upload-time = "2026-08-14T21:35:13.746Z" },
{ url = "https://files.pythonhosted.org/packages/36/38/67fedfd2cb77516ef0066b1642f487dba0eb3006493cf3475b15f5b8b228/ty-0.0.73-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16981e15fdceedb37d0aff76c5ac25914595dfee2675af95335550064251ad22", size = 13665497, upload-time = "2026-08-19T03:12:19.286Z" }, { url = "https://files.pythonhosted.org/packages/31/2f/03bba754d2613f640df168335c41f83f41db150bb515839c60d80e3a7880/ty-0.0.72-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30caf658feb8ffb250d9e9e47107657a78f5f3425c227df1664d8df2ebe38880", size = 13590392, upload-time = "2026-08-14T21:35:16.839Z" },
{ url = "https://files.pythonhosted.org/packages/8e/b3/154f4dd48ec5eebc186ab4b822c6e62f982fc5ddfd262d6e3903c2acba44/ty-0.0.73-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:644b2bec8a2e2e4957a942ae81d6cff5571c489bb5a8675e4d3886de537a694d", size = 13351231, upload-time = "2026-08-19T03:12:21.353Z" }, { url = "https://files.pythonhosted.org/packages/04/c7/03c67f00e63005ec41585653dc3096064570b1e6273742baae2798cd242f/ty-0.0.72-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:27bdc012ddfbeec8948e4a6036c0dc39ac7cf2c8ec7c7d48dc7d2fd56d57b399", size = 13309629, upload-time = "2026-08-14T21:35:19.169Z" },
{ url = "https://files.pythonhosted.org/packages/35/5f/d462496903fbe453fb76363f8478be929c8e6ff21e6928c57dcd7e5fa21f/ty-0.0.73-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:338d565be3186f50ff8e9d10483685549c2d23f0754485d5ede3b54f4319188a", size = 12782586, upload-time = "2026-08-19T03:12:23.667Z" }, { url = "https://files.pythonhosted.org/packages/c1/df/102d3b264eb7f2a58dd11952f229bb5150bb5668d176a6154976a6675981/ty-0.0.72-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:802c5970a77d7739e6f499921fbb6984fb7ad8a31d95e1ff42fd46f3642e4f3b", size = 12734028, upload-time = "2026-08-14T21:35:22.099Z" },
{ url = "https://files.pythonhosted.org/packages/87/52/ec6d24b74abe3ec324204c1c71e6d0c6c76a17ffc15fd51d603b0a302abe/ty-0.0.73-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:11c7b6d839309d2c102cb3a4c03d817176bbfab5b2fccc95a75ec5c9597421c9", size = 13247134, upload-time = "2026-08-19T03:12:25.956Z" }, { url = "https://files.pythonhosted.org/packages/61/85/d0737c8c54d0ba67366ddfb9f31d88edf0b02299e65923e6945ae60ebcb5/ty-0.0.72-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:47dce65114fdc615c68ca0edb393b433df0956447e4267df0e264137a789598d", size = 13174832, upload-time = "2026-08-14T21:35:24.71Z" },
{ url = "https://files.pythonhosted.org/packages/26/20/cc74650fec56a54786c6d7c89e09576fcad3092be34cf21715d39a406a9b/ty-0.0.73-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:488572db7ff97fb50ea36a76250f2d617c9727d143da6c7bf0623276eb0fc507", size = 12309344, upload-time = "2026-08-19T03:12:28.122Z" }, { url = "https://files.pythonhosted.org/packages/1e/31/497f5a96c36d9b586ab6afe0574986835c6fd5b835a89773d2bec4711b49/ty-0.0.72-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:325144fa07e2675d0faa337fcc864213c272a499eb0cfe5bde2fdc62282d27bc", size = 12215005, upload-time = "2026-08-14T21:35:26.892Z" },
{ url = "https://files.pythonhosted.org/packages/89/bd/4b0a9087f4315d7fbadf77a3ce44c816cc9ffabed1ced06cc5be81fbc414/ty-0.0.73-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1b958ebceefbbf594e59eb8d3d55bbd033ce634026fcba3e4bc3179e78e45bb7", size = 12502319, upload-time = "2026-08-19T03:12:30.128Z" }, { url = "https://files.pythonhosted.org/packages/df/7d/46e65b17b4966c7cd0140f134380d33d8e84fe6efccd761533ce793dc502/ty-0.0.72-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a5c9f15d0f58e43707d8848274be1821a0ef408eccb8aa7dda28a4a9eddf7640", size = 12421298, upload-time = "2026-08-14T21:35:29.301Z" },
{ url = "https://files.pythonhosted.org/packages/11/80/0a925074911fe111912ea29d9eed309bcc183f43d2fb3eef07db056a0beb/ty-0.0.73-py3-none-musllinux_1_2_i686.whl", hash = "sha256:91a32993b3c34e42c3f323ad6c0399cb596bd1c27e9b7f20db7cd64c1067b68e", size = 12753688, upload-time = "2026-08-19T03:12:32.433Z" }, { url = "https://files.pythonhosted.org/packages/08/2a/12ada4ec17700b3cb1d4fd3bc3e5b1852df9e6885288429318cade87b3c1/ty-0.0.72-py3-none-musllinux_1_2_i686.whl", hash = "sha256:8ee508d64b381871529cc22c412b41071bf5e908b7aa5d66a38f3f6b2573a806", size = 12669242, upload-time = "2026-08-14T21:35:31.444Z" },
{ url = "https://files.pythonhosted.org/packages/24/6b/aeccaf89efbc2e112bd415340a22e2669ec998aa397242503e747b712ca4/ty-0.0.73-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:bab8a19fbf51f479bddb2a12c5fabfe52f918a5590362321ed5d89b44eb62c15", size = 13069050, upload-time = "2026-08-19T03:12:35.398Z" }, { url = "https://files.pythonhosted.org/packages/1c/1a/4692536880790fb550ed6d44a6096778dc71bb112f2c6d615cebb01a57e5/ty-0.0.72-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3699e2ec7921d44da79d6b089f7bf239b2cc53c4e45a5a38430adc34ee9e9a55", size = 12988199, upload-time = "2026-08-14T21:35:33.749Z" },
{ url = "https://files.pythonhosted.org/packages/d7/3e/eae485fd86c1585943fd4e1746b0757b2da01e2c43136ebe8c686fe1c7f1/ty-0.0.73-py3-none-win32.whl", hash = "sha256:03347a612f0fa020b19bfd8dbd521db6ecc75d377a3e4d4f6e6c2e62871da4cc", size = 12053187, upload-time = "2026-08-19T03:12:37.565Z" }, { url = "https://files.pythonhosted.org/packages/9a/0d/f5e5a50322e9c45865e7b7a428ba6cd6527387cf0f2472492ac3cf746243/ty-0.0.72-py3-none-win32.whl", hash = "sha256:f25f72a67bd36cd247707c4784e52fad0b6b4f42a1b7dd14804110fa95c486ed", size = 11939708, upload-time = "2026-08-14T21:35:36.006Z" },
{ url = "https://files.pythonhosted.org/packages/a7/01/9b8b983786e3ce34924e372e8b76b92b508273ab65c589fc7e88cc03ee17/ty-0.0.73-py3-none-win_amd64.whl", hash = "sha256:cedd05122ded0b5dcc55431a370e974b747f99c41c290a3d2ab8c1867f197519", size = 12693838, upload-time = "2026-08-19T03:12:39.483Z" }, { url = "https://files.pythonhosted.org/packages/3f/4e/8af3534b2e4214e6184a5a59c34101e94a68d578f081f97b995866bab1bf/ty-0.0.72-py3-none-win_amd64.whl", hash = "sha256:cdeee869341717e1736cea2e2d7856738c6957c320f584ed2f68c8f90100d2f5", size = 12643876, upload-time = "2026-08-14T21:35:38.141Z" },
{ url = "https://files.pythonhosted.org/packages/ea/88/25333bbfea6a5dc064371d2002d3d4807db90b84d5448f9106b2712b0fbc/ty-0.0.73-py3-none-win_arm64.whl", hash = "sha256:e47068f8369dea5d641a26a2ad0a947a320b02ff87099b07e95de0323245a4dc", size = 12443573, upload-time = "2026-08-19T03:12:41.449Z" }, { url = "https://files.pythonhosted.org/packages/ff/ea/a2606e654c7276bd08586391a2525b0af3f3bf60228a8c57b2d248f273f9/ty-0.0.72-py3-none-win_arm64.whl", hash = "sha256:1bd3ac3ed4424a6d6990a85dc388556aea012bd752de21349a84b685951de0d8", size = 12394857, upload-time = "2026-08-14T21:35:40.277Z" },
] ]
[[package]] [[package]]
@@ -1387,7 +1387,7 @@ wheels = [
[[package]] [[package]]
name = "zensical" name = "zensical"
version = "0.0.56" version = "0.0.54"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "click" }, { name = "click" },
@@ -1399,20 +1399,20 @@ dependencies = [
{ name = "pyyaml" }, { name = "pyyaml" },
{ name = "tomli" }, { name = "tomli" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/f8/7d/18bb725a659352e9af0940a3d879c5edcff5d86fec3ac15ce500d484d9d3/zensical-0.0.56.tar.gz", hash = "sha256:c359163800d1c3a8c39af48f4e2869fcfc2b4fc00d28652bd2a5b0330c36530c", size = 3997416, upload-time = "2026-08-18T15:46:47.283Z" } sdist = { url = "https://files.pythonhosted.org/packages/75/7e/343a78c0c9da1954d2f0a4d47ca778baac48bb18c6b9c0c6260e7974976e/zensical-0.0.54.tar.gz", hash = "sha256:4de205dbb323d0a443e2ebf3fef77e93e3c1493c34a58d205e7f3631dd7745af", size = 3992024, upload-time = "2026-08-13T16:04:49.297Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/8b/4b/6810aec6e670451f39639039b570a43d90bc1d4a3b93cf13316ccf6bad11/zensical-0.0.56-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:5135ea3aa5d1358503fc1903e866c191873b138028bc1ab170abac3a4b537ffe", size = 12874712, upload-time = "2026-08-18T15:46:18.378Z" }, { url = "https://files.pythonhosted.org/packages/8b/cf/13e887c303fd5c786c09f83362382a42d10292fca633a95067de6a6591a3/zensical-0.0.54-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f7177a3b6647e4ee47864ab02e5141f9e923dd57b9fa96e5dc5da4228daff505", size = 12893082, upload-time = "2026-08-13T16:04:17.337Z" },
{ url = "https://files.pythonhosted.org/packages/97/98/23445d8ed708088dd6d9d51674f8836b77c53aab280360d7aa7a206bea8e/zensical-0.0.56-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:a22ae2329ba755c6e58e1fe5967ca429d580d346a73cf467ad5185377e2cf809", size = 12764552, upload-time = "2026-08-18T15:46:20.746Z" }, { url = "https://files.pythonhosted.org/packages/82/ee/7fe1418fa31bc120cf9eb0fbce9e021c40752206ce993a7bc652c011f890/zensical-0.0.54-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:7b23c3b0c720885891b220c3e562074d93eb940314823d91875c6246976bd9b2", size = 12778626, upload-time = "2026-08-13T16:04:19.906Z" },
{ url = "https://files.pythonhosted.org/packages/14/bd/ab89450728b0a55e6a52a86060b38a362a52a1b9f02eda7fef68b729eb2e/zensical-0.0.56-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03c70ef328e31cce0e31739acdd6679e9272bc7a3016ca5eafaa16dcfda460c9", size = 13212920, upload-time = "2026-08-18T15:46:22.977Z" }, { url = "https://files.pythonhosted.org/packages/61/b8/0420115270c1a22a2d4a1598f89dadc4e933eb7aa85539b72f3532acc5b6/zensical-0.0.54-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c012eb0ec20fda5794e90b4906b7401c77b712e2acc7f9f65026935368539da", size = 13225462, upload-time = "2026-08-13T16:04:22.663Z" },
{ url = "https://files.pythonhosted.org/packages/bf/5a/969fd9a461204392a9266a544c185fbb223714b306534661dcbb7d290be7/zensical-0.0.56-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ffc50153a50292078357a5052e7a6b3e20a818523792ffcbeace70418b761eb", size = 13146652, upload-time = "2026-08-18T15:46:25.773Z" }, { url = "https://files.pythonhosted.org/packages/8d/48/0dfdd3e00fb3b807de702383ef5f4e9cf0d2791fee0e8a56f39bd590f16b/zensical-0.0.54-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f0e851d26ba4f7397db3b3532e534c0572a9826bbd451ee0a00e649c030dcb38", size = 13158184, upload-time = "2026-08-13T16:04:24.958Z" },
{ url = "https://files.pythonhosted.org/packages/22/b3/28a7c8dea8fe2edbba75a664efbde797b5922651ea92ffc480947ab22197/zensical-0.0.56-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88bba0e339d36647ce638a42b4ba96f2521b59ea54be74c314340be7e401f94b", size = 13530946, upload-time = "2026-08-18T15:46:28.19Z" }, { url = "https://files.pythonhosted.org/packages/b1/2c/f1fb1f5387108b206f985cdb394bbdc730557cc339e63067ded9b3565263/zensical-0.0.54-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a48583da6f485e2373845d4332882c9e9d504302dbd2731c589543584df5b087", size = 13536772, upload-time = "2026-08-13T16:04:27.373Z" },
{ url = "https://files.pythonhosted.org/packages/d6/a6/d55a18c6e041b788af1d19e4d8c61ee9b8a7de5b9cc79ffa7bc9565e3a28/zensical-0.0.56-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b7a37f6ac38ac218e3e0dd311b58c468603963e38ee23f2e25672dcd6e9c17b", size = 13178741, upload-time = "2026-08-18T15:46:30.378Z" }, { url = "https://files.pythonhosted.org/packages/a3/97/3224b3dd5d76cebddf9725ae871a5a6a8f2de177be59d802232d00073d8b/zensical-0.0.54-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da7781a906623fb7bc278cc874ec6f80691c8753b90fbdf90a451abb046ae204", size = 13191901, upload-time = "2026-08-13T16:04:30.295Z" },
{ url = "https://files.pythonhosted.org/packages/6c/4f/a07da2f761cfd6a27faf9e8d5a9475c396a9a0ca37424a0b67cab7ae4d2c/zensical-0.0.56-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:5f6d850bce3184422b37b9d98ef4d123047a47446844793251cabc04bd55f8f0", size = 13389836, upload-time = "2026-08-18T15:46:32.847Z" }, { url = "https://files.pythonhosted.org/packages/b2/00/b1f55530c8df4331e1f209ec605816a578142f124b5f8078a9d984514d63/zensical-0.0.54-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:75aff1c01f6104dd0e79d08c87bd595da13db3e1f6da55fc9933ebff3e94fdd5", size = 13402956, upload-time = "2026-08-13T16:04:32.899Z" },
{ url = "https://files.pythonhosted.org/packages/34/aa/697ef9846b0e2071de4d03b0472e2658380ea9271bd01235f4ffaeef1978/zensical-0.0.56-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:d18944a4a111050b8f571e73d4e2475d7ce62ce78b64f09bcf33bb11cc346e1c", size = 13419551, upload-time = "2026-08-18T15:46:35.112Z" }, { url = "https://files.pythonhosted.org/packages/c7/6e/58df496c2742600df3a2f273ba52b8ced1ce357340b825af9f1bb0f2042e/zensical-0.0.54-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:1469c5d551a0ea2d0fcf922046a263d2afcff5f03ce3bb443e286970d04ff9f2", size = 13431462, upload-time = "2026-08-13T16:04:35.518Z" },
{ url = "https://files.pythonhosted.org/packages/97/6b/20e1b2443951d5182b3fc2ee54c200ea00c09bd8901a479be4147c94361b/zensical-0.0.56-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:3937029ec5091d577c2a05ebccd38fde97fab28662d165ead66d566689b2a6df", size = 13579878, upload-time = "2026-08-18T15:46:37.381Z" }, { url = "https://files.pythonhosted.org/packages/5b/85/70ae775db7865be2434bc39e9b4ff1c7988978d796a7ff9d49294e7410c7/zensical-0.0.54-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:31c354f98b3374b9bcab65278a278dbec66125c997f7f1a5ee9501f413b87b9c", size = 13587289, upload-time = "2026-08-13T16:04:37.954Z" },
{ url = "https://files.pythonhosted.org/packages/53/c7/9c3b400b8a7d78cc169b7a78d4f74a90f9114209034a604f04051f48037c/zensical-0.0.56-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:346a1cdbad157633d185f79039a97c8b4f8c2b40be7d7efd56d72622f40247b0", size = 13526142, upload-time = "2026-08-18T15:46:39.949Z" }, { url = "https://files.pythonhosted.org/packages/0f/ca/05e6b3e04323810bbf4da9240b8b710740fac82ec01659b14c86e44aa88e/zensical-0.0.54-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:85ef75654e7845aa65f1392af771771566f33faac452a2aed004ba367454f812", size = 13534594, upload-time = "2026-08-13T16:04:41.006Z" },
{ url = "https://files.pythonhosted.org/packages/0e/f6/7a6f2a513054071e44a504d7157684f00ac5e5e5f741eadc78ab6c80642c/zensical-0.0.56-cp310-abi3-win32.whl", hash = "sha256:a06681046f74b5bdc506d22af8fcef505b2450e45538c7f01a5e028f4e50c6f7", size = 12433218, upload-time = "2026-08-18T15:46:42.329Z" }, { url = "https://files.pythonhosted.org/packages/14/70/be34910c13632f85911f505bd9a4d1bb53d46c8498d20ebb18570bbe4b7b/zensical-0.0.54-cp310-abi3-win32.whl", hash = "sha256:b56c80a8cd234666afb917fb1ed8467104f6857b1acabd66f60ef1b8a0daa66f", size = 12448180, upload-time = "2026-08-13T16:04:43.486Z" },
{ url = "https://files.pythonhosted.org/packages/6b/ed/5b497c75fb1fd5845f3ec2f0b11b5e01f18f14c297041cd2f20d30d8b6a4/zensical-0.0.56-cp310-abi3-win_amd64.whl", hash = "sha256:c557985f12d042c15dcb7d577543d81ceee9281f96d591d265310b673437a325", size = 12702823, upload-time = "2026-08-18T15:46:44.836Z" }, { url = "https://files.pythonhosted.org/packages/a1/c6/a965265946555023dc0e159a41038502190882669d177dfa59b1dd3d580b/zensical-0.0.54-cp310-abi3-win_amd64.whl", hash = "sha256:f5a602986c4123a349cfd075c8d494096a2a2db74422169d65f8092872e48dfa", size = 12712264, upload-time = "2026-08-13T16:04:46.155Z" },
] ]
[[package]] [[package]]