diff --git a/common/git.py b/common/git.py index 2296fa7088bedb..6b662e57191d03 100644 --- a/common/git.py +++ b/common/git.py @@ -4,27 +4,27 @@ @cache -def get_commit(cwd: str = None, branch: str = "HEAD") -> str: +def get_commit(cwd: str | None = None, branch: str = "HEAD") -> str: return run_cmd_default(["git", "rev-parse", branch], cwd=cwd) @cache -def get_commit_date(cwd: str = None, commit: str = "HEAD") -> str: +def get_commit_date(cwd: str | None = None, commit: str = "HEAD") -> str: return run_cmd_default(["git", "show", "--no-patch", "--format='%ct %ci'", commit], cwd=cwd) @cache -def get_short_branch(cwd: str = None) -> str: +def get_short_branch(cwd: str | None = None) -> str: return run_cmd_default(["git", "rev-parse", "--abbrev-ref", "HEAD"], cwd=cwd) @cache -def get_branch(cwd: str = None) -> str: +def get_branch(cwd: str | None = None) -> str: return run_cmd_default(["git", "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], cwd=cwd) @cache -def get_origin(cwd: str = None) -> str: +def get_origin(cwd: str | None = None) -> str: try: local_branch = run_cmd(["git", "name-rev", "--name-only", "HEAD"], cwd=cwd) tracking_remote = run_cmd(["git", "config", "branch." + local_branch + ".remote"], cwd=cwd) @@ -34,7 +34,7 @@ def get_origin(cwd: str = None) -> str: @cache -def get_normalized_origin(cwd: str = None) -> str: +def get_normalized_origin(cwd: str | None = None) -> str: return get_origin(cwd) \ .replace("git@", "", 1) \ .replace(".git", "", 1) \ diff --git a/common/pid.py b/common/pid.py index e3fa8afdf40550..c00431e2c3bc82 100644 --- a/common/pid.py +++ b/common/pid.py @@ -2,16 +2,15 @@ from numbers import Number class PIDController: - def __init__(self, k_p, k_i, k_d=0., pos_limit=1e308, neg_limit=-1e308, rate=100): - self._k_p = k_p - self._k_i = k_i - self._k_d = k_d - if isinstance(self._k_p, Number): - self._k_p = [[0], [self._k_p]] - if isinstance(self._k_i, Number): - self._k_i = [[0], [self._k_i]] - if isinstance(self._k_d, Number): - self._k_d = [[0], [self._k_d]] + _k_p: list[list[float]] + _k_i: list[list[float]] + _k_d: list[list[float]] + + def __init__(self, k_p: float | list[list[float]], k_i: float | list[list[float]], + k_d: float | list[list[float]] = 0., pos_limit=1e308, neg_limit=-1e308, rate=100): + self._k_p: list[list[float]] = [[0.0], [float(k_p)]] if isinstance(k_p, Number) else k_p + self._k_i: list[list[float]] = [[0.0], [float(k_i)]] if isinstance(k_i, Number) else k_i + self._k_d: list[list[float]] = [[0.0], [float(k_d)]] if isinstance(k_d, Number) else k_d self.set_limits(pos_limit, neg_limit) diff --git a/common/prefix.py b/common/prefix.py index 207f8477d76e5b..7b9e316b263b62 100644 --- a/common/prefix.py +++ b/common/prefix.py @@ -9,7 +9,7 @@ from openpilot.system.hardware.hw import DEFAULT_DOWNLOAD_CACHE_ROOT class OpenpilotPrefix: - def __init__(self, prefix: str = None, create_dirs_on_enter: bool = True, clean_dirs_on_exit: bool = True, shared_download_cache: bool = False): + def __init__(self, prefix: str | None = None, create_dirs_on_enter: bool = True, clean_dirs_on_exit: bool = True, shared_download_cache: bool = False): self.prefix = prefix if prefix else str(uuid.uuid4().hex[0:15]) self.msgq_path = os.path.join(Paths.shm_path(), self.prefix) self.create_dirs_on_enter = create_dirs_on_enter diff --git a/common/swaglog.py b/common/swaglog.py index d009f00e76177d..576fea6ea6fd24 100644 --- a/common/swaglog.py +++ b/common/swaglog.py @@ -18,6 +18,8 @@ def get_file_handler(): return handler class SwaglogRotatingFileHandler(BaseRotatingHandler): + last_rollover: float + def __init__(self, base_filename, interval=60, max_bytes=1024*256, backup_count=2500, encoding=None): super().__init__(base_filename, mode="a", encoding=encoding, delay=True) self.base_filename = base_filename @@ -27,7 +29,6 @@ def __init__(self, base_filename, interval=60, max_bytes=1024*256, backup_count= self.log_files = self.get_existing_logfiles() log_indexes = [f.split(".")[-1] for f in self.log_files] self.last_file_idx = max([int(i) for i in log_indexes if i.isdigit()] or [-1]) - self.last_rollover = None self.doRollover() def _open(self): diff --git a/pyproject.toml b/pyproject.toml index d5dc95e1b1a7b1..ec448dae0f8cad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ docs = [ testing = [ "coverage", "hypothesis ==6.47.*", - "mypy", + "ty", "pytest", "pytest-cpp", "pytest-subtests", @@ -180,8 +180,10 @@ ignore-words-list = "bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,w builtin = "clear,rare,informal,code,names,en-GB_to_en-US" skip = "./third_party/*, ./tinygrad/*, ./tinygrad_repo/*, ./msgq/*, ./panda/*, ./opendbc/*, ./opendbc_repo/*, ./rednose/*, ./rednose_repo/*, ./teleoprtc/*, ./teleoprtc_repo/*, *.po, uv.lock, *.onnx, ./cereal/gen/*, */c_generated_code/*, docs/assets/*, tools/plotjuggler/layouts/*, selfdrive/assets/offroad/mici_fcc.html" -[tool.mypy] -python_version = "3.11" +[tool.ty.environment] +python-version = "3.11" + +[tool.ty.src] exclude = [ "cereal/", "msgq/", @@ -198,23 +200,18 @@ exclude = [ "third_party/", ] -# third-party packages -ignore_missing_imports=true - -# helpful warnings -warn_redundant_casts=true -warn_unreachable=true -warn_unused_ignores=true - -# restrict dynamic typing -warn_return_any=true +[tool.ty.rules] +# third-party packages (maps to mypy's ignore_missing_imports) +unresolved-import = "ignore" -# allow implicit optionals for default args -implicit_optional = true +# helpful warnings (maps to mypy's warn_* options) +redundant-cast = "warn" +unused-ignore-comment = "warn" -local_partial_types=true -explicit_package_bases=true -disable_error_code = "annotation-unchecked" +# TODO: fix and remove these +possibly-missing-attribute = "ignore" # 260 warnings +invalid-argument-type = "ignore" # 111 errors +unresolved-attribute = "ignore" # 96 errors # https://beta.ruff.rs/docs/configuration/#using-pyprojecttoml [tool.ruff] diff --git a/scripts/lint/lint.sh b/scripts/lint/lint.sh index 578c63cd1894d4..5581171e8feb65 100755 --- a/scripts/lint/lint.sh +++ b/scripts/lint/lint.sh @@ -55,7 +55,7 @@ function run_tests() { run "check_nomerge_comments" $DIR/check_nomerge_comments.sh $ALL_FILES if [[ -z "$FAST" ]]; then - run "mypy" mypy $PYTHON_FILES + run "ty" ty check run "codespell" codespell $ALL_FILES fi @@ -69,7 +69,7 @@ function help() { echo "" echo -e "${BOLD}${UNDERLINE}Tests:${NC}" echo -e " ${BOLD}ruff${NC}" - echo -e " ${BOLD}mypy${NC}" + echo -e " ${BOLD}ty${NC}" echo -e " ${BOLD}codespell${NC}" echo -e " ${BOLD}check_added_large_files${NC}" echo -e " ${BOLD}check_shebang_scripts_are_executable${NC}" @@ -81,11 +81,11 @@ function help() { echo " Specify tests to skip separated by spaces" echo "" echo -e "${BOLD}${UNDERLINE}Examples:${NC}" - echo " op lint mypy ruff" - echo " Only run the mypy and ruff tests" + echo " op lint ty ruff" + echo " Only run the ty and ruff tests" echo "" - echo " op lint --skip mypy ruff" - echo " Skip the mypy and ruff tests" + echo " op lint --skip ty ruff" + echo " Skip the ty and ruff tests" echo "" echo " op lint" echo " Run all the tests" diff --git a/selfdrive/car/car_specific.py b/selfdrive/car/car_specific.py index 9e166a44d7730c..ec7f2ed77feb42 100644 --- a/selfdrive/car/car_specific.py +++ b/selfdrive/car/car_specific.py @@ -122,7 +122,7 @@ def update(self, CS: car.CarState, CS_prev: car.CarState, CC: car.CarControl): events.add(EventName.speedTooLow) # TODO: this needs to be implemented generically in carState struct - # if CC.eps_timer_soft_disable_alert: # type: ignore[attr-defined] + # if CC.eps_timer_soft_disable_alert: # events.add(EventName.steerTimeLimit) elif self.CP.brand == 'hyundai': diff --git a/selfdrive/car/card.py b/selfdrive/car/card.py index 27b04ae65e73f1..d3cdb1efbfc472 100755 --- a/selfdrive/car/card.py +++ b/selfdrive/car/card.py @@ -106,6 +106,7 @@ def __init__(self, CI=None, RI=None) -> None: # continue onto next fingerprinting step in pandad self.params.put_bool("FirmwareQueryDone", True) else: + assert RI is not None self.CI, self.CP = CI, CI.CP self.RI = RI diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index 94f5b332319ebc..c4eac492c036cb 100644 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -243,7 +243,7 @@ def test_panda_safety_rx_checks(self): # Don't check relay malfunction on disabled routes (relay closed), # or before fingerprinting is done (elm327 and noOutput) - if self.openpilot_enabled and t / 1e4 > self.car_safety_mode_frame: + if self.openpilot_enabled and self.car_safety_mode_frame is not None and t / 1e4 > self.car_safety_mode_frame: self.assertFalse(self.safety.get_relay_malfunction()) else: self.safety.set_relay_malfunction(False) diff --git a/selfdrive/debug/car/hyundai_enable_radar_points.py b/selfdrive/debug/car/hyundai_enable_radar_points.py index 93f5949eac2fa8..52beb3879d638e 100755 --- a/selfdrive/debug/car/hyundai_enable_radar_points.py +++ b/selfdrive/debug/car/hyundai_enable_radar_points.py @@ -101,11 +101,11 @@ class ConfigValues(NamedTuple): uds_client = UdsClient(panda, 0x7D0, bus=args.bus) print("\n[START DIAGNOSTIC SESSION]") - session_type : SESSION_TYPE = 0x07 # type: ignore + session_type = SESSION_TYPE(0x07) uds_client.diagnostic_session_control(session_type) print("[HARDWARE/SOFTWARE VERSION]") - fw_version_data_id : DATA_IDENTIFIER_TYPE = 0xf100 # type: ignore + fw_version_data_id = DATA_IDENTIFIER_TYPE(0xf100) fw_version = uds_client.read_data_by_identifier(fw_version_data_id) print(fw_version) if fw_version not in SUPPORTED_FW_VERSIONS.keys(): @@ -113,7 +113,7 @@ class ConfigValues(NamedTuple): sys.exit(1) print("[GET CONFIGURATION]") - config_data_id : DATA_IDENTIFIER_TYPE = 0x0142 # type: ignore + config_data_id = DATA_IDENTIFIER_TYPE(0x0142) current_config = uds_client.read_data_by_identifier(config_data_id) config_values = SUPPORTED_FW_VERSIONS[fw_version] new_config = config_values.default_config if args.default else config_values.tracks_enabled diff --git a/selfdrive/debug/car/vw_mqb_config.py b/selfdrive/debug/car/vw_mqb_config.py index 3c55642e40b854..f96a031894d728 100755 --- a/selfdrive/debug/car/vw_mqb_config.py +++ b/selfdrive/debug/car/vw_mqb_config.py @@ -55,7 +55,7 @@ class ACCESS_TYPE_LEVEL_1(IntEnum): sw_ver = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_ECU_SOFTWARE_VERSION_NUMBER).decode("utf-8") component = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.SYSTEM_NAME_OR_ENGINE_TYPE).decode("utf-8") odx_file = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.ODX_FILE).decode("utf-8").rstrip('\x00') - current_coding = uds_client.read_data_by_identifier(VOLKSWAGEN_DATA_IDENTIFIER_TYPE.CODING) # type: ignore + current_coding = uds_client.read_data_by_identifier(VOLKSWAGEN_DATA_IDENTIFIER_TYPE.CODING) coding_text = current_coding.hex() print("\nEPS diagnostic data\n") @@ -72,6 +72,7 @@ class ACCESS_TYPE_LEVEL_1(IntEnum): print("Timeout fetching data from EPS") quit() + assert current_coding is not None coding_variant, current_coding_array, coding_byte, coding_bit = None, None, 0, 0 coding_length = len(current_coding) @@ -126,9 +127,9 @@ class ACCESS_TYPE_LEVEL_1(IntEnum): new_coding = current_coding[0:coding_byte] + new_byte.to_bytes(1, "little") + current_coding[coding_byte+1:] try: - seed = uds_client.security_access(ACCESS_TYPE_LEVEL_1.REQUEST_SEED) # type: ignore + seed = uds_client.security_access(ACCESS_TYPE_LEVEL_1.REQUEST_SEED) key = struct.unpack("!I", seed)[0] + 28183 # yeah, it's like that - uds_client.security_access(ACCESS_TYPE_LEVEL_1.SEND_KEY, struct.pack("!I", key)) # type: ignore + uds_client.security_access(ACCESS_TYPE_LEVEL_1.SEND_KEY, struct.pack("!I", key)) except (NegativeResponseError, MessageTimeoutError): print("Security access failed!") print("Open the hood and retry (disables the \"diagnostic firewall\" on newer vehicles)") @@ -148,7 +149,7 @@ class ACCESS_TYPE_LEVEL_1(IntEnum): uds_client.write_data_by_identifier(DATA_IDENTIFIER_TYPE.PROGRAMMING_DATE, prog_date) tester_num = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.CALIBRATION_REPAIR_SHOP_CODE_OR_CALIBRATION_EQUIPMENT_SERIAL_NUMBER) uds_client.write_data_by_identifier(DATA_IDENTIFIER_TYPE.REPAIR_SHOP_CODE_OR_TESTER_SERIAL_NUMBER, tester_num) - uds_client.write_data_by_identifier(VOLKSWAGEN_DATA_IDENTIFIER_TYPE.CODING, new_coding) # type: ignore + uds_client.write_data_by_identifier(VOLKSWAGEN_DATA_IDENTIFIER_TYPE.CODING, new_coding) except (NegativeResponseError, MessageTimeoutError): print("Writing new configuration failed!") print("Make sure the comma processes are stopped: tmux kill-session -t comma") @@ -156,7 +157,7 @@ class ACCESS_TYPE_LEVEL_1(IntEnum): try: # Read back result just to make 100% sure everything worked - current_coding_text = uds_client.read_data_by_identifier(VOLKSWAGEN_DATA_IDENTIFIER_TYPE.CODING).hex() # type: ignore + current_coding_text = uds_client.read_data_by_identifier(VOLKSWAGEN_DATA_IDENTIFIER_TYPE.CODING).hex() print(f" New coding: {current_coding_text}") except (NegativeResponseError, MessageTimeoutError): print("Reading back updated coding failed!") diff --git a/selfdrive/debug/cpu_usage_stat.py b/selfdrive/debug/cpu_usage_stat.py index 397e9f35f5221c..089685103f7cda 100755 --- a/selfdrive/debug/cpu_usage_stat.py +++ b/selfdrive/debug/cpu_usage_stat.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# type: ignore ''' System tools like top/htop can only show current cpu usage values, so I write this script to do statistics jobs. Features: diff --git a/selfdrive/debug/cycle_alerts.py b/selfdrive/debug/cycle_alerts.py index 11e75a7a8e3be3..00fa33ac63a2f0 100755 --- a/selfdrive/debug/cycle_alerts.py +++ b/selfdrive/debug/cycle_alerts.py @@ -99,8 +99,7 @@ def cycle_alerts(duration=200, is_metric=False): alert = AM.process_alerts(frame, []) print(alert) for _ in range(duration): - dat = messaging.new_message() - dat.init('selfdriveState') + dat = messaging.new_message('selfdriveState') dat.selfdriveState.enabled = False if alert: @@ -112,8 +111,7 @@ def cycle_alerts(duration=200, is_metric=False): dat.selfdriveState.alertSound = alert.audible_alert pm.send('selfdriveState', dat) - dat = messaging.new_message() - dat.init('deviceState') + dat = messaging.new_message('deviceState') dat.deviceState.started = True pm.send('deviceState', dat) diff --git a/selfdrive/debug/fingerprint_from_route.py b/selfdrive/debug/fingerprint_from_route.py index 179ff4c83833fb..5af46488dbb3c3 100755 --- a/selfdrive/debug/fingerprint_from_route.py +++ b/selfdrive/debug/fingerprint_from_route.py @@ -7,7 +7,7 @@ def get_fingerprint(lr): # TODO: make this a nice tool for car ports. should also work with qlogs for FW - fw = None + fw = [] vin = None msgs = {} for msg in lr: diff --git a/selfdrive/debug/fuzz_fw_fingerprint.py b/selfdrive/debug/fuzz_fw_fingerprint.py index fa99e6bfbe8e9e..b4276c0c1af93d 100755 --- a/selfdrive/debug/fuzz_fw_fingerprint.py +++ b/selfdrive/debug/fuzz_fw_fingerprint.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# type: ignore import random from collections import defaultdict diff --git a/selfdrive/debug/live_cpu_and_temp.py b/selfdrive/debug/live_cpu_and_temp.py index ee0524ec9d4d06..ff048603ea4d12 100755 --- a/selfdrive/debug/live_cpu_and_temp.py +++ b/selfdrive/debug/live_cpu_and_temp.py @@ -74,9 +74,11 @@ def proc_name(proc): print(f"CPU {100.0 * np.mean(cores):.2f}% - RAM: {last_mem:.2f}% - Temp {last_temp:.2f}C") + cur_proclog_t: int = sm.logMonoTime['procLog'] if args.cpu and prev_proclog is not None and prev_proclog_t is not None: + assert prev_proclog_t is not None procs: dict[str, float] = defaultdict(float) - dt = (sm.logMonoTime['procLog'] - prev_proclog_t) / 1e9 + dt = (cur_proclog_t - prev_proclog_t) / 1e9 for proc in m.procs: try: name = proc_name(proc) @@ -103,4 +105,4 @@ def proc_name(proc): print() prev_proclog = m - prev_proclog_t = sm.logMonoTime['procLog'] + prev_proclog_t = cur_proclog_t diff --git a/selfdrive/debug/measure_torque_time_to_max.py b/selfdrive/debug/measure_torque_time_to_max.py index 7052dccf7de283..e99aeae464b8e9 100755 --- a/selfdrive/debug/measure_torque_time_to_max.py +++ b/selfdrive/debug/measure_torque_time_to_max.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# type: ignore import os import argparse diff --git a/selfdrive/debug/test_fw_query_on_routes.py b/selfdrive/debug/test_fw_query_on_routes.py index 1216b7299f01c0..ab539e4feba49f 100755 --- a/selfdrive/debug/test_fw_query_on_routes.py +++ b/selfdrive/debug/test_fw_query_on_routes.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# type: ignore from collections import defaultdict import argparse diff --git a/selfdrive/locationd/calibrationd.py b/selfdrive/locationd/calibrationd.py index 03c044982e3fe6..036f5822d25589 100755 --- a/selfdrive/locationd/calibrationd.py +++ b/selfdrive/locationd/calibrationd.py @@ -47,7 +47,7 @@ def is_calibration_valid(rpy: np.ndarray) -> bool: - return (PITCH_LIMITS[0] < rpy[1] < PITCH_LIMITS[1]) and (YAW_LIMITS[0] < rpy[2] < YAW_LIMITS[1]) # type: ignore + return (PITCH_LIMITS[0] < rpy[1] < PITCH_LIMITS[1]) and (YAW_LIMITS[0] < rpy[2] < YAW_LIMITS[1]) def sanity_clip(rpy: np.ndarray) -> np.ndarray: @@ -92,7 +92,7 @@ def reset(self, rpy_init: np.ndarray = RPY_INIT, valid_blocks: int = 0, wide_from_device_euler_init: np.ndarray = WIDE_FROM_DEVICE_EULER_INIT, height_init: np.ndarray = HEIGHT_INIT, - smooth_from: np.ndarray = None) -> None: + smooth_from: np.ndarray | None = None) -> None: if not np.isfinite(rpy_init).all(): self.rpy = RPY_INIT.copy() else: diff --git a/selfdrive/locationd/helpers.py b/selfdrive/locationd/helpers.py index 2a3ac8b8610fff..73c4d8bf352552 100644 --- a/selfdrive/locationd/helpers.py +++ b/selfdrive/locationd/helpers.py @@ -94,7 +94,7 @@ def is_calculable(self) -> bool: def add_point(self, x: float, y: float) -> None: raise NotImplementedError - def get_points(self, num_points: int = None) -> Any: + def get_points(self, num_points: int | None = None) -> Any: points = np.vstack([x.arr for x in self.buckets.values()]) if num_points is None: return points diff --git a/selfdrive/locationd/paramsd.py b/selfdrive/locationd/paramsd.py index b4084fe5bc9783..fd03d3d09368f7 100755 --- a/selfdrive/locationd/paramsd.py +++ b/selfdrive/locationd/paramsd.py @@ -127,8 +127,8 @@ def handle_log(self, t: float, which: str, msg: capnp._DynamicStructReader): if not self.active: # Reset time when stopped so uncertainty doesn't grow - self.kf.filter.set_filter_time(t) # type: ignore - self.kf.filter.reset_rewind() # type: ignore + self.kf.filter.set_filter_time(t) + self.kf.filter.reset_rewind() def get_msg(self, valid: bool, debug: bool = False) -> capnp._DynamicStructBuilder: x = self.kf.x diff --git a/selfdrive/locationd/test/test_locationd_scenarios.py b/selfdrive/locationd/test/test_locationd_scenarios.py index 0ea7ac183f2dc4..98749e81dce1a6 100644 --- a/selfdrive/locationd/test/test_locationd_scenarios.py +++ b/selfdrive/locationd/test/test_locationd_scenarios.py @@ -33,9 +33,9 @@ class Scenario(Enum): def get_select_fields_data(logs): def get_nested_keys(msg, keys): - val = None + val = msg for key in keys: - val = getattr(msg if val is None else val, key) if isinstance(key, str) else val[key] + val = getattr(val, key) if isinstance(key, str) else val[key] return val lp = [x.livePose for x in logs if x.which() == 'livePose'] data = defaultdict(list) diff --git a/selfdrive/selfdrived/alertmanager.py b/selfdrive/selfdrived/alertmanager.py index 251d32ba9a2abf..385c276a948c95 100644 --- a/selfdrive/selfdrived/alertmanager.py +++ b/selfdrive/selfdrived/alertmanager.py @@ -13,7 +13,7 @@ OFFROAD_ALERTS = json.load(f) -def set_offroad_alert(alert: str, show_alert: bool, extra_text: str = None) -> None: +def set_offroad_alert(alert: str, show_alert: bool, extra_text: str | None = None) -> None: if show_alert: a = copy.copy(OFFROAD_ALERTS[alert]) a['extra'] = extra_text or '' diff --git a/selfdrive/test/fuzzy_generation.py b/selfdrive/test/fuzzy_generation.py index 94eb0dfaa62a75..131dab47b23a85 100644 --- a/selfdrive/test/fuzzy_generation.py +++ b/selfdrive/test/fuzzy_generation.py @@ -44,7 +44,7 @@ def rec(field_type: capnp.lib.capnp._DynamicStructReader) -> st.SearchStrategy: except capnp.lib.capnp.KjException: return self.generate_struct(field.schema) - def generate_struct(self, schema: capnp.lib.capnp._StructSchema, event: str = None) -> st.SearchStrategy[dict[str, Any]]: + def generate_struct(self, schema: capnp.lib.capnp._StructSchema, event: str | None = None) -> st.SearchStrategy[dict[str, Any]]: single_fill: tuple[str, ...] = (event,) if event else (self.draw(st.sampled_from(schema.union_fields)),) if schema.union_fields else () fields_to_generate = schema.non_union_fields + single_fill return st.fixed_dictionaries({field: self.generate_field(schema.fields[field]) for field in fields_to_generate if not field.endswith('DEPRECATED')}) diff --git a/selfdrive/test/process_replay/migration.py b/selfdrive/test/process_replay/migration.py index 33b363cfd9496c..145e2cfa48d849 100644 --- a/selfdrive/test/process_replay/migration.py +++ b/selfdrive/test/process_replay/migration.py @@ -1,8 +1,8 @@ from collections import defaultdict -from collections.abc import Callable import capnp import functools import traceback +from typing import Protocol from cereal import messaging, car, log from opendbc.car.fingerprints import MIGRATION @@ -19,11 +19,16 @@ MessageWithIndex = tuple[int, capnp.lib.capnp._DynamicStructReader] MigrationOps = tuple[list[tuple[int, capnp.lib.capnp._DynamicStructReader]], list[capnp.lib.capnp._DynamicStructReader], list[int]] -MigrationFunc = Callable[[list[MessageWithIndex]], MigrationOps] + + +class MigrationCallable(Protocol): + inputs: list[str] + product: str | None + def __call__(self, msgs: list[MessageWithIndex]) -> MigrationOps: ... # rules for migration functions -# 1. must use the decorator @migration(inputs=[...], product="...") and MigrationFunc signature +# 1. must use the decorator @migration(inputs=[...], product="...") and MigrationCallable signature # 2. it only gets the messages that are in the inputs list # 3. product is the message type created by the migration function, and the function will be skipped if product type already exists in lr # 4. it must return a list of operations to be applied to the logreader (replace, add, delete) @@ -55,7 +60,7 @@ def migrate_all(lr: LogIterable, manager_states: bool = False, panda_states: boo return migrate(lr, migrations) -def migrate(lr: LogIterable, migration_funcs: list[MigrationFunc]): +def migrate(lr: LogIterable, migration_funcs: list[MigrationCallable]): lr = list(lr) grouped = defaultdict(list) for i, msg in enumerate(lr): diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index 1144b7955e766d..1249ff13b5e13a 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -211,7 +211,7 @@ def _setup_vision_ipc(self, all_msgs: LogIterable, frs: dict[str, Any]): def _start_process(self): if self.capture is not None: - self.process.launcher = LauncherWithCapture(self.capture, self.process.launcher) + self.process.launcher = LauncherWithCapture(self.capture, self.process.launcher) # type: ignore[assignment] # monkeypatch self.process.prepare() self.process.start() @@ -610,9 +610,9 @@ def replay_process_with_name(name: str | Iterable[str], lr: LogIterable, *args, def replay_process( - cfg: ProcessConfig | Iterable[ProcessConfig], lr: LogIterable, frs: dict[str, FrameReader] = None, - fingerprint: str = None, return_all_logs: bool = False, custom_params: dict[str, Any] = None, - captured_output_store: dict[str, dict[str, str]] = None, disable_progress: bool = False + cfg: ProcessConfig | Iterable[ProcessConfig], lr: LogIterable, frs: dict[str, FrameReader] | None = None, + fingerprint: str | None = None, return_all_logs: bool = False, custom_params: dict[str, Any] | None = None, + captured_output_store: dict[str, dict[str, str]] | None = None, disable_progress: bool = False ) -> list[capnp._DynamicStructReader]: if isinstance(cfg, Iterable): cfgs = list(cfg) diff --git a/selfdrive/test/process_replay/regen.py b/selfdrive/test/process_replay/regen.py index ec35a5c3acf438..c501a4b25083c1 100755 --- a/selfdrive/test/process_replay/regen.py +++ b/selfdrive/test/process_replay/regen.py @@ -16,7 +16,7 @@ def regen_segment( - lr: LogIterable, frs: dict[str, Any] = None, + lr: LogIterable, frs: dict[str, Any] | None = None, processes: Iterable[ProcessConfig] = CONFIGS, disable_tqdm: bool = False ) -> list[capnp._DynamicStructReader]: all_msgs = sorted(lr, key=lambda m: m.logMonoTime) diff --git a/selfdrive/test/update_ci_routes.py b/selfdrive/test/update_ci_routes.py index 2bf06b48603e51..54e1c88718f9f8 100755 --- a/selfdrive/test/update_ci_routes.py +++ b/selfdrive/test/update_ci_routes.py @@ -19,7 +19,7 @@ DEST = OpenpilotCIContainer -def upload_route(path: str, exclude_patterns: Iterable[str] = None) -> None: +def upload_route(path: str, exclude_patterns: Iterable[str] | None = None) -> None: if exclude_patterns is None: exclude_patterns = [r'dcamera\.hevc'] diff --git a/selfdrive/ui/layouts/home.py b/selfdrive/ui/layouts/home.py index cd6ae600ef3a3a..a8404a20c3b49c 100644 --- a/selfdrive/ui/layouts/home.py +++ b/selfdrive/ui/layouts/home.py @@ -39,7 +39,7 @@ def __init__(self): self.current_state = HomeLayoutState.HOME self.last_refresh = 0 - self.settings_callback: callable | None = None + self.settings_callback: Callable[[], None] | None = None self.update_available = False self.alert_count = 0 diff --git a/selfdrive/ui/layouts/main.py b/selfdrive/ui/layouts/main.py index 702854f98a7eb1..090107613685c5 100644 --- a/selfdrive/ui/layouts/main.py +++ b/selfdrive/ui/layouts/main.py @@ -41,7 +41,7 @@ def __init__(self): if not self._onboarding_window.completed: gui_app.set_modal_overlay(self._onboarding_window) - def _render(self, _): + def _render(self, rect): self._handle_onroad_transition() self._render_main_content() diff --git a/selfdrive/ui/layouts/onboarding.py b/selfdrive/ui/layouts/onboarding.py index 5d61c1c95a3293..f3a736d1f43467 100644 --- a/selfdrive/ui/layouts/onboarding.py +++ b/selfdrive/ui/layouts/onboarding.py @@ -85,7 +85,7 @@ def _update_state(self): if len(self._image_objs): self._textures.append(gui_app._load_texture_from_image(self._image_objs.pop(0))) - def _render(self, _): + def _render(self, rect): # Safeguard against fast tapping step = min(self._step, len(self._textures) - 1) rl.draw_texture(self._textures[step], 0, 0, rl.WHITE) @@ -116,7 +116,7 @@ def __init__(self, on_accept=None, on_decline=None): self._decline_btn = Button(tr("Decline"), click_callback=on_decline) self._accept_btn = Button(tr("Agree"), button_style=ButtonStyle.PRIMARY, click_callback=on_accept) - def _render(self, _): + def _render(self, rect): welcome_x = self._rect.x + 165 welcome_y = self._rect.y + 165 welcome_rect = rl.Rectangle(welcome_x, welcome_y, self._rect.width - welcome_x, 90) @@ -153,7 +153,7 @@ def _on_uninstall_clicked(self): ui_state.params.put_bool("DoUninstall", True) gui_app.request_close() - def _render(self, _): + def _render(self, rect): btn_y = self._rect.y + self._rect.height - 160 - 45 btn_width = (self._rect.width - 45 * 3) / 2 self._back_btn.render(rl.Rectangle(self._rect.x + 45, btn_y, btn_width, 160)) @@ -200,7 +200,7 @@ def _on_completed_training(self): ui_state.params.put("CompletedTrainingVersion", training_version) gui_app.set_modal_overlay(None) - def _render(self, _): + def _render(self, rect): if self._training_guide is None: self._training_guide = TrainingGuide(completed_callback=self._on_completed_training) diff --git a/selfdrive/ui/mici/layouts/home.py b/selfdrive/ui/mici/layouts/home.py index 9152bdc7fa789e..4d521e2755a670 100644 --- a/selfdrive/ui/mici/layouts/home.py +++ b/selfdrive/ui/mici/layouts/home.py @@ -58,7 +58,7 @@ def _update_state(self): network_type = NETWORK_TYPES[ui_state.sm['deviceState'].networkType.raw] self._network_status = f"{network_type} {strength_text}" - def _render(self, _): + def _render(self, rect): # draw status status_rect = rl.Rectangle(self._rect.x, self._rect.y, self._rect.width, 40) gui_label(status_rect, self._system_status, font_size=HEAD_BUTTON_FONT_SIZE, color=DEFAULT_TEXT_COLOR, @@ -177,7 +177,7 @@ def _get_version_text(self) -> tuple[str, str, str, str] | None: return None - def _render(self, _): + def _render(self, rect): # TODO: why is there extra space here to get it to be flush? text_pos = rl.Vector2(self.rect.x - 2 + HOME_PADDING, self.rect.y - 16) self._openpilot_label.set_position(text_pos.x, text_pos.y) diff --git a/selfdrive/ui/mici/layouts/main.py b/selfdrive/ui/mici/layouts/main.py index b52f9ed39a06f9..c828b0482142d6 100644 --- a/selfdrive/ui/mici/layouts/main.py +++ b/selfdrive/ui/mici/layouts/main.py @@ -76,7 +76,7 @@ def _scroll_to(self, layout: Widget): layout_x = int(layout.rect.x) self._scroller.scroll_to(layout_x, smooth=True) - def _render(self, _): + def _render(self, rect): # Initial show event if self._current_mode is None: self._set_mode(MainState.MAIN) diff --git a/selfdrive/ui/mici/layouts/offroad_alerts.py b/selfdrive/ui/mici/layouts/offroad_alerts.py index 60f64b31b064ce..eb6762390b03eb 100644 --- a/selfdrive/ui/mici/layouts/offroad_alerts.py +++ b/selfdrive/ui/mici/layouts/offroad_alerts.py @@ -131,7 +131,7 @@ def update_alert_data(self, alert_data: AlertData): self.alert_data = alert_data self._update_content() - def _render(self, _): + def _render(self, rect): if not self.alert_data.visible or not self.alert_data.text: return diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index abf772ce5806a5..c36c70672f34a1 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -119,7 +119,7 @@ def _update_state(self): if device.awake: ui_state.params.put_bool("IsDriverViewEnabled", True) - def _render(self, _): + def _render(self, rect): self._dialog.render(self._rect) rl.draw_rectangle_gradient_v(int(self._rect.x), int(self._rect.y + self._rect.height - self._title_header.rect.height * 1.5 - 32), @@ -232,7 +232,7 @@ def _advance_step(self): if self._completed_callback: self._completed_callback() - def _render(self, _): + def _render(self, rect): if self._step < len(self._steps): self._steps[self._step].render(self._rect) return -1 @@ -253,7 +253,7 @@ def _on_uninstall(self): ui_state.params.put_bool("DoUninstall", True) gui_app.request_close() - def _render(self, _): + def _render(self, rect): self._warning_header.render(rl.Rectangle( self._rect.x + 16, self._rect.y + 16, @@ -341,7 +341,7 @@ def _on_completed_training(self): ui_state.params.put("CompletedTrainingVersion", training_version) self.close() - def _render(self, _): + def _render(self, rect): if self._state == OnboardingState.TERMS: self._terms.render(self._rect) elif self._state == OnboardingState.ONBOARDING: diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index 988c823a9944da..df5827067cfe7e 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -20,7 +20,7 @@ from openpilot.system.ui.widgets import Widget, NavWidget from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.widgets.label import MiciLabel -from openpilot.system.ui.widgets.html_render import HtmlModal, HtmlRenderer +from openpilot.system.ui.widgets.html_render import HtmlRenderer from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID @@ -97,7 +97,7 @@ def __init__(self): self._serial_number_label = MiciLabel("serial", 48, color=header_color, font_weight=FontWeight.DISPLAY) self._serial_number_text_label = MiciLabel(params.get("HardwareSerial") or 'N/A', 32, width=max_width, color=subheader_color, font_weight=FontWeight.ROMAN) - def _render(self, _): + def _render(self, rect): self._dongle_id_label.set_position(self._rect.x + 20, self._rect.y - 10) self._dongle_id_label.render() @@ -269,7 +269,7 @@ class DeviceLayoutMici(NavWidget): def __init__(self, back_callback: Callable): super().__init__() - self._fcc_dialog: HtmlModal | None = None + self._fcc_dialog: MiciFccModal | None = None self._driver_camera: DriverCameraDialog | None = None self._training_guide: TrainingGuide | None = None diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 793bdcf4a0b55d..deab53ae02877a 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -16,7 +16,7 @@ def normalize_ssid(ssid: str) -> str: class LoadingAnimation(Widget): - def _render(self, _): + def _render(self, rect): cx = int(self._rect.x + 70) cy = int(self._rect.y + self._rect.height / 2 - 50) @@ -52,7 +52,7 @@ def set_current_network(self, network: Network): def set_scale(self, scale: float): self._scale = scale - def _render(self, _): + def _render(self, rect): if self._network is None: return @@ -99,7 +99,7 @@ def set_current_network(self, network: Network): self._network = network self._wifi_icon.set_current_network(network) - def _render(self, _): + def _render(self, rect): if self._network.is_connected: selected_x = int(self._rect.x - self._selected_txt.width / 2) selected_y = int(self._rect.y + (self._rect.height - self._selected_txt.height) / 2) @@ -153,7 +153,7 @@ def set_full(self, full: bool): def set_label(self, text: str): self._label.set_text(text) - def _render(self, _): + def _render(self, rect): if self._full: bg_txt = self._bg_full_pressed_txt if self.is_pressed and self.enabled else self._bg_full_txt else: @@ -184,7 +184,7 @@ def _handle_mouse_release(self, mouse_pos: MousePos): confirm_callback=self._forget_network) gui_app.set_modal_overlay(dlg, callback=self._open_network_manage_page) - def _render(self, _): + def _render(self, rect): bg_txt = self._bg_pressed_txt if self.is_pressed else self._bg_txt rl.draw_texture(bg_txt, int(self._rect.x + self.HORIZONTAL_MARGIN), int(self._rect.y), rl.WHITE) @@ -275,7 +275,7 @@ def _is_connecting(self): is_connecting = self._connecting() == self._network.ssid return is_connecting - def _render(self, _): + def _render(self, rect): self._wifi_icon.render(rl.Rectangle( self._rect.x + 32, self._rect.y + (self._rect.height - self._connect_btn.rect.height - self._wifi_icon.rect.height) / 2, @@ -447,7 +447,7 @@ def _update_state(self): if self.is_pressed: self._last_interaction_time = rl.get_time() - def _render(self, _): + def _render(self, rect): # Update Scroller layout and restore current selection whenever buttons are updated, before first render current_selection = self.get_selected_option() if self._restore_selection and current_selection in self._networks: @@ -455,7 +455,7 @@ def _render(self, _): BigMultiOptionDialog._on_option_selected(self, current_selection, smooth_scroll=False) self._restore_selection = None - super()._render(_) + super()._render(rect) if not self._networks: self._loading_animation.render(self._rect) diff --git a/selfdrive/ui/mici/onroad/augmented_road_view.py b/selfdrive/ui/mici/onroad/augmented_road_view.py index 71ca03cccfac94..2c7851a3a45719 100644 --- a/selfdrive/ui/mici/onroad/augmented_road_view.py +++ b/selfdrive/ui/mici/onroad/augmented_road_view.py @@ -121,7 +121,7 @@ def _handle_mouse_event(self, mouse_event: MouseEvent): self._is_swiping = False self._is_swiping_left = False - def _render(self, _): + def _render(self, rect): """Render the bookmark icon.""" if self._offset_filter.x > 0: icon_x = self.rect.x + self.rect.width - round(self._offset_filter.x) @@ -182,7 +182,7 @@ def _handle_mouse_release(self, mouse_pos: MousePos): if not self._bookmark_icon.interacting(): super()._handle_mouse_release(mouse_pos) - def _render(self, _): + def _render(self, rect): start_draw = time.monotonic() self._switch_stream_if_needed(ui_state.sm) diff --git a/selfdrive/ui/mici/onroad/confidence_ball.py b/selfdrive/ui/mici/onroad/confidence_ball.py index a5c95470f54c1e..617538a7656834 100644 --- a/selfdrive/ui/mici/onroad/confidence_ball.py +++ b/selfdrive/ui/mici/onroad/confidence_ball.py @@ -41,7 +41,7 @@ def _update_state(self): self._confidence_filter.update((1 - max(ui_state.sm['modelV2'].meta.disengagePredictions.brakeDisengageProbs or [1])) * (1 - max(ui_state.sm['modelV2'].meta.disengagePredictions.steerOverrideProbs or [1]))) - def _render(self, _): + def _render(self, rect): content_rect = rl.Rectangle( self.rect.x + self.rect.width - SIDE_PANEL_WIDTH, self.rect.y, diff --git a/selfdrive/ui/mici/onroad/driver_camera_dialog.py b/selfdrive/ui/mici/onroad/driver_camera_dialog.py index 9adb660d8b1c97..fe3fc8b70dea69 100644 --- a/selfdrive/ui/mici/onroad/driver_camera_dialog.py +++ b/selfdrive/ui/mici/onroad/driver_camera_dialog.py @@ -60,7 +60,7 @@ def hide_event(self): ui_state.params.put_bool("IsDriverViewEnabled", False) device.reset_interactive_timeout() - def _handle_mouse_release(self, _): + def _handle_mouse_release(self, mouse_pos): ui_state.params.remove("DriverTooDistracted") def __del__(self): diff --git a/selfdrive/ui/mici/onroad/driver_state.py b/selfdrive/ui/mici/onroad/driver_state.py index 080083c3e2ec6f..eca94b450d0ccf 100644 --- a/selfdrive/ui/mici/onroad/driver_state.py +++ b/selfdrive/ui/mici/onroad/driver_state.py @@ -78,7 +78,7 @@ def effective_active(self) -> bool: """Returns True if dmoji should appear active (either actually active or forced)""" return bool(self._force_active or self._is_active) - def _render(self, _): + def _render(self, rect): if DEBUG: rl.draw_rectangle_lines_ex(self._rect, 1, rl.RED) diff --git a/selfdrive/ui/mici/onroad/hud_renderer.py b/selfdrive/ui/mici/onroad/hud_renderer.py index 7f489ccf9813ad..c823bf75d99a13 100644 --- a/selfdrive/ui/mici/onroad/hud_renderer.py +++ b/selfdrive/ui/mici/onroad/hud_renderer.py @@ -52,7 +52,7 @@ def __init__(self): self._txt_turn_intent_left: rl.Texture = gui_app.texture('icons_mici/turn_intent_left.png', 50, 19) self._txt_turn_intent_right: rl.Texture = gui_app.texture('icons_mici/turn_intent_right.png', 50, 19) - def _render(self, _): + def _render(self, rect): if self._turn_intent_alpha_filter.x > 1e-2: turn_intent_texture = self._txt_turn_intent_right if self._turn_intent_direction == 1 else self._txt_turn_intent_left src_rect = rl.Rectangle(0, 0, turn_intent_texture.width, turn_intent_texture.height) diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index be08e0fee3768f..e4059c692c7862 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -51,7 +51,7 @@ def __init__(self, icon: str, red: bool = False): def set_enable_pressed_state(self, pressed: bool): self._press_state_enabled = pressed - def _render(self, _): + def _render(self, rect): # draw background txt_bg = self._txt_btn_bg if not self._red else self._txt_btn_red_bg if not self.enabled: @@ -71,7 +71,7 @@ def _render(self, _): class BigCircleToggle(BigCircleButton): - def __init__(self, icon: str, toggle_callback: Callable = None): + def __init__(self, icon: str, toggle_callback: Callable | None = None): super().__init__(icon, False) self._toggle_callback = toggle_callback @@ -92,8 +92,8 @@ def _handle_mouse_release(self, mouse_pos: MousePos): if self._toggle_callback: self._toggle_callback(self._checked) - def _render(self, _): - super()._render(_) + def _render(self, rect): + super()._render(rect) # draw status icon rl.draw_texture(self._txt_toggle_enabled if self._checked else self._txt_toggle_disabled, @@ -208,7 +208,7 @@ def _update_state(self): self._scroll_timer = 0 self._scroll_offset = 0 - def _render(self, _): + def _render(self, rect): # draw _txt_default_bg txt_bg = self._txt_default_bg if not self.enabled: @@ -251,7 +251,7 @@ def _render(self, _): class BigToggle(BigButton): - def __init__(self, text: str, value: str = "", initial_state: bool = False, toggle_callback: Callable = None): + def __init__(self, text: str, value: str = "", initial_state: bool = False, toggle_callback: Callable | None = None): super().__init__(text, value, "") self._checked = initial_state self._toggle_callback = toggle_callback @@ -279,8 +279,8 @@ def _draw_pill(self, x: float, y: float, checked: bool): else: rl.draw_texture(self._txt_disabled_toggle, int(x), int(y), rl.WHITE) - def _render(self, _): - super()._render(_) + def _render(self, rect): + super()._render(rect) x = self._rect.x + self._rect.width - self._txt_enabled_toggle.width y = self._rect.y @@ -288,8 +288,8 @@ def _render(self, _): class BigMultiToggle(BigToggle): - def __init__(self, text: str, options: list[str], toggle_callback: Callable = None, - select_callback: Callable = None): + def __init__(self, text: str, options: list[str], toggle_callback: Callable | None = None, + select_callback: Callable | None = None): super().__init__(text, "", toggle_callback=toggle_callback) assert len(options) > 0 self._options = options @@ -313,8 +313,8 @@ def _handle_mouse_release(self, mouse_pos: MousePos): if self._select_callback: self._select_callback(self.value) - def _render(self, _): - BigButton._render(self, _) + def _render(self, rect): + BigButton._render(self, rect) checked_idx = self._options.index(self.value) @@ -327,11 +327,12 @@ def _render(self, _): class BigMultiParamToggle(BigMultiToggle): - def __init__(self, text: str, param: str, options: list[str], toggle_callback: Callable = None, - select_callback: Callable = None): + def __init__(self, text: str, param: str, options: list[str], toggle_callback: Callable | None = None, + select_callback: Callable | None = None): super().__init__(text, options, toggle_callback, select_callback) self._param = param + assert Params is not None self._params = Params() self._load_value() @@ -345,9 +346,10 @@ def _handle_mouse_release(self, mouse_pos: MousePos): class BigParamControl(BigToggle): - def __init__(self, text: str, param: str, toggle_callback: Callable = None): + def __init__(self, text: str, param: str, toggle_callback: Callable | None = None): super().__init__(text, "", toggle_callback=toggle_callback) self.param = param + assert Params is not None self.params = Params() self.set_checked(self.params.get_bool(self.param, False)) @@ -361,9 +363,10 @@ def refresh(self): # TODO: param control base class class BigCircleParamControl(BigCircleToggle): - def __init__(self, icon: str, param: str, toggle_callback: Callable = None): + def __init__(self, icon: str, param: str, toggle_callback: Callable | None = None): super().__init__(icon, toggle_callback) self._param = param + assert Params is not None self.params = Params() self.set_checked(self.params.get_bool(self._param, False)) diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 3d9aa3f9e247a3..ff8d407690f531 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -40,7 +40,7 @@ def right_btn_callback_wrapper(): # move to right side self._right_btn._rect.x = self._rect.x + self._rect.width - self._right_btn._rect.width - def _render(self, _) -> DialogResult: + def _render(self, rect) -> DialogResult: """ Allows `gui_app.set_modal_overlay(BigDialog(...))`. The overlay runner keeps calling until result != NO_ACTION. @@ -62,8 +62,8 @@ def __init__(self, self._title = title self._description = description - def _render(self, _) -> DialogResult: - super()._render(_) + def _render(self, rect) -> DialogResult: + super()._render(rect) # draw title # TODO: we desperately need layouts @@ -124,7 +124,7 @@ def _update_state(self): if self._swiping_away and not self._slider.confirmed: self._slider.reset() - def _render(self, _) -> DialogResult: + def _render(self, rect) -> DialogResult: self._slider.render(self._rect) return self._ret @@ -137,7 +137,7 @@ def __init__(self, hint: str, default_text: str = "", minimum_length: int = 1, - confirm_callback: Callable[[str], None] = None): + confirm_callback: Callable[[str], None] | None = None): super().__init__(None, None) self._hint_label = UnifiedLabel(hint, font_size=35, text_color=rl.Color(255, 255, 255, int(255 * 0.35)), font_weight=FontWeight.MEDIUM) @@ -178,7 +178,7 @@ def _update_state(self): else: self._backspace_held_time = None - def _render(self, _): + def _render(self, rect): text_input_size = 35 # draw current text so far below everything. text floats left but always stays in view @@ -296,7 +296,7 @@ def set_selected(self, selected: bool): self._selected = selected self._rect.height = self.SELECTED_HEIGHT if selected else self.HEIGHT - def _render(self, _): + def _render(self, rect): if DEBUG: rl.draw_rectangle_lines_ex(self._rect, 1, rl.Color(0, 255, 0, 255)) @@ -317,7 +317,7 @@ class BigMultiOptionDialog(BigDialogBase): BACK_TOUCH_AREA_PERCENTAGE = 0.1 def __init__(self, options: list[str], default: str | None, - right_btn: str | None = 'check', right_btn_callback: Callable[[], None] = None): + right_btn: str | None = 'check', right_btn_callback: Callable[[], None] | None = None): super().__init__(right_btn, right_btn_callback=right_btn_callback) self._options = options if default is not None: @@ -391,8 +391,8 @@ def _update_state(self): self._selected_option_changed() self._last_selected_option = self._selected_option - def _render(self, _): - super()._render(_) + def _render(self, rect): + super()._render(rect) self._scroller.render(self._rect) return self._ret diff --git a/selfdrive/ui/mici/widgets/side_button.py b/selfdrive/ui/mici/widgets/side_button.py index 4803b6d208c931..00141fb0863bd5 100644 --- a/selfdrive/ui/mici/widgets/side_button.py +++ b/selfdrive/ui/mici/widgets/side_button.py @@ -22,7 +22,7 @@ def __init__(self, btn_type: str): btn_img_pressed_path = f"icons_mici/buttons/button_side_{btn_type}_pressed.png" self._txt_btn, self._txt_btn_back = gui_app.texture(btn_img_path, 100, 224), gui_app.texture(btn_img_pressed_path, 100, 224) - def _render(self, _) -> bool: + def _render(self, rect) -> bool: x = int(self._rect.x + 12) y = int(self._rect.y + (self._rect.height - self._txt_btn.height) / 2) rl.draw_texture(self._txt_btn if not self.is_pressed else self._txt_btn_back, diff --git a/selfdrive/ui/onroad/augmented_road_view.py b/selfdrive/ui/onroad/augmented_road_view.py index 1f202141c3806b..325d4580950f64 100644 --- a/selfdrive/ui/onroad/augmented_road_view.py +++ b/selfdrive/ui/onroad/augmented_road_view.py @@ -103,11 +103,11 @@ def _render(self, rect): msg.uiDebug.drawTimeMillis = (time.monotonic() - start_draw) * 1000 self._pm.send('uiDebug', msg) - def _handle_mouse_press(self, _): + def _handle_mouse_press(self, mouse_pos): if not self._hud_renderer.user_interacting() and self._click_callback is not None: self._click_callback() - def _handle_mouse_release(self, _): + def _handle_mouse_release(self, mouse_pos): # We only call click callback on press if not interacting with HUD pass diff --git a/selfdrive/ui/onroad/driver_camera_dialog.py b/selfdrive/ui/onroad/driver_camera_dialog.py index 543ea35e812cb6..637bb6ad8a0aa8 100644 --- a/selfdrive/ui/onroad/driver_camera_dialog.py +++ b/selfdrive/ui/onroad/driver_camera_dialog.py @@ -21,8 +21,8 @@ def stop_dmonitoringmodeld(self): ui_state.params.put_bool("IsDriverViewEnabled", False) gui_app.set_modal_overlay(None) - def _handle_mouse_release(self, _): - super()._handle_mouse_release(_) + def _handle_mouse_release(self, mouse_pos): + super()._handle_mouse_release(mouse_pos) self.stop_dmonitoringmodeld() def _render(self, rect): diff --git a/selfdrive/ui/onroad/exp_button.py b/selfdrive/ui/onroad/exp_button.py index e5d81714130d67..68d6c93edc4173 100644 --- a/selfdrive/ui/onroad/exp_button.py +++ b/selfdrive/ui/onroad/exp_button.py @@ -32,8 +32,8 @@ def _update_state(self) -> None: self._experimental_mode = selfdrive_state.experimentalMode self._engageable = selfdrive_state.engageable or selfdrive_state.enabled - def _handle_mouse_release(self, _): - super()._handle_mouse_release(_) + def _handle_mouse_release(self, mouse_pos): + super()._handle_mouse_release(mouse_pos) if self._is_toggle_allowed(): new_mode = not self._experimental_mode self._params.put_bool("ExperimentalMode", new_mode) diff --git a/selfdrive/ui/tests/profile_onroad.py b/selfdrive/ui/tests/profile_onroad.py index fde4f25ffed40d..9c93cba9a9099a 100755 --- a/selfdrive/ui/tests/profile_onroad.py +++ b/selfdrive/ui/tests/profile_onroad.py @@ -49,7 +49,7 @@ def mock_update(timeout=None): sm.recv_frame[service] = sm.frame sm.valid[service] = True sm.frame += 1 - ui_state.sm.update = mock_update + ui_state.sm.update = mock_update # type: ignore[assignment] # monkeypatch for profiling if __name__ == "__main__": diff --git a/selfdrive/ui/tests/test_translations.py b/selfdrive/ui/tests/test_translations.py index 3177814f9f068e..a509200a6d4350 100644 --- a/selfdrive/ui/tests/test_translations.py +++ b/selfdrive/ui/tests/test_translations.py @@ -53,7 +53,7 @@ def test_finished_translations(self): - that translation is not empty - that translation format arguments are consistent """ - tr_xml = ET.parse(os.path.join(TRANSLATIONS_DIR, f"{self.file}.ts")) + tr_xml = ET.parse(TRANSLATIONS_DIR / f"{self.file}.ts") for context in tr_xml.getroot(): for message in context.iterfind("message"): @@ -75,8 +75,8 @@ def test_finished_translations(self): else: assert translation.text is not None, f"Ensure translation is completed: {source_text}" - source_args = FORMAT_ARG.findall(source_text) - translation_args = FORMAT_ARG.findall(translation.text) + source_args = FORMAT_ARG.findall(source_text or "") + translation_args = FORMAT_ARG.findall(translation.text or "") assert sorted(source_args) == sorted(translation_args), \ f"Ensure format arguments are consistent: `{source_text}` vs. `{translation.text}`" @@ -108,13 +108,13 @@ def test_bad_language(self): banned_words = {line.strip() for line in response.text.splitlines()} - for context in ET.parse(os.path.join(TRANSLATIONS_DIR, f"{self.file}.ts")).getroot(): + for context in ET.parse(TRANSLATIONS_DIR / f"{self.file}.ts").getroot(): for message in context.iterfind("message"): translation = message.find("translation") if translation.get("type") == "unfinished": continue - translation_text = " ".join([t.text for t in translation.findall("numerusform")]) if message.get("numerus") == "yes" else translation.text + translation_text = " ".join([t.text or "" for t in translation.findall("numerusform")]) if message.get("numerus") == "yes" else translation.text if not translation_text: continue diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index 481ac111beb9f5..b879c71bce5d8f 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -248,7 +248,8 @@ class TestUI: def __init__(self): os.environ["SCALE"] = os.getenv("SCALE", "1") os.environ["BIG"] = "1" - sys.modules["mouseinfo"] = False + # Prevent mouseinfo import by setting to a falsy value (intentional hack) + sys.modules["mouseinfo"] = False # type: ignore[invalid-assignment] def setup(self): # Seed minimal offroad state diff --git a/selfdrive/ui/translations/auto_translate.py b/selfdrive/ui/translations/auto_translate.py index 6251e03397176f..9354790f94f9e5 100755 --- a/selfdrive/ui/translations/auto_translate.py +++ b/selfdrive/ui/translations/auto_translate.py @@ -18,7 +18,7 @@ "The following sentence or word is in the GUI of a software called openpilot, translate it accordingly." -def get_language_files(languages: list[str] = None) -> dict[str, pathlib.Path]: +def get_language_files(languages: list[str] | None = None) -> dict[str, pathlib.Path]: files = {} with open(TRANSLATIONS_LANGUAGES) as fp: diff --git a/selfdrive/ui/update_translations.py b/selfdrive/ui/update_translations.py index bded80b2e5bdf0..9f985feead6269 100755 --- a/selfdrive/ui/update_translations.py +++ b/selfdrive/ui/update_translations.py @@ -11,9 +11,9 @@ def update_translations(): files = [] for root, _, filenames in chain(os.walk(SYSTEM_UI_DIR), - os.walk(os.path.join(UI_DIR, "widgets")), - os.walk(os.path.join(UI_DIR, "layouts")), - os.walk(os.path.join(UI_DIR, "onroad"))): + os.walk(UI_DIR / "widgets"), + os.walk(UI_DIR / "layouts"), + os.walk(UI_DIR / "onroad")): for filename in filenames: if filename.endswith(".py"): files.append(os.path.relpath(os.path.join(root, filename), BASEDIR)) @@ -28,7 +28,7 @@ def update_translations(): # Generate/update translation files for each language for name in multilang.languages.values(): - if os.path.exists(os.path.join(TRANSLATIONS_DIR, f"app_{name}.po")): + if os.path.exists(TRANSLATIONS_DIR / f"app_{name}.po"): cmd = f"msgmerge --update --no-fuzzy-matching --backup=none --sort-output {TRANSLATIONS_DIR}/app_{name}.po {POT_FILE}" ret = os.system(cmd) assert ret == 0 diff --git a/selfdrive/ui/widgets/offroad_alerts.py b/selfdrive/ui/widgets/offroad_alerts.py index 802243ff3eb162..dc07b982551c3a 100644 --- a/selfdrive/ui/widgets/offroad_alerts.py +++ b/selfdrive/ui/widgets/offroad_alerts.py @@ -65,7 +65,7 @@ def __init__(self, text: str | Callable[[], str], style: ButtonStyle = ButtonSty def text(self) -> str: return self._text() if callable(self._text) else self._text - def _render(self, _): + def _render(self, rect): text_size = measure_text_cached(gui_app.font(FontWeight.MEDIUM), self.text, AlertConstants.FONT_SIZE) self._rect.width = max(text_size.x + 60 * 2, self._min_width) self._rect.height = AlertConstants.BUTTON_HEIGHT diff --git a/system/athena/athenad.py b/system/athena/athenad.py index 3b71a9c31f5a0a..b52ef21ba63702 100755 --- a/system/athena/athenad.py +++ b/system/athena/athenad.py @@ -314,7 +314,7 @@ def upload_handler(end_event: threading.Event) -> None: cloudlog.exception("athena.upload_handler.exception") -def _do_upload(upload_item: UploadItem, callback: Callable = None) -> requests.Response: +def _do_upload(upload_item: UploadItem, callback: Callable | None = None) -> requests.Response: path = upload_item.path compress = False @@ -805,7 +805,7 @@ def backoff(retries: int) -> int: return random.randrange(0, min(128, int(2 ** retries))) -def main(exit_event: threading.Event = None): +def main(exit_event: threading.Event | None = None): try: set_core_affinity([0, 1, 2, 3]) except Exception: diff --git a/system/athena/tests/test_athenad.py b/system/athena/tests/test_athenad.py index 99ac3b1c6b66e1..9849637a23acb1 100644 --- a/system/athena/tests/test_athenad.py +++ b/system/athena/tests/test_athenad.py @@ -60,7 +60,7 @@ class TestAthenadMethods: @classmethod def setup_class(cls): cls.SOCKET_PORT = 45454 - athenad.Api = MockApi + athenad.Api = MockApi # type: ignore[assignment] # monkeypatch for testing athenad.LOCAL_PORT_WHITELIST = {cls.SOCKET_PORT} def setup_method(self): @@ -97,7 +97,7 @@ def _wait_for_upload(): break @staticmethod - def _create_file(file: str, parent: str = None, data: bytes = b'') -> str: + def _create_file(file: str, parent: str | None = None, data: bytes = b'') -> str: fn = os.path.join(Paths.log_root() if parent is None else parent, file) os.makedirs(os.path.dirname(fn), exist_ok=True) with open(fn, 'wb') as f: diff --git a/system/loggerd/tests/loggerd_tests_common.py b/system/loggerd/tests/loggerd_tests_common.py index 87c3da65c2f2fb..309792557aca70 100644 --- a/system/loggerd/tests/loggerd_tests_common.py +++ b/system/loggerd/tests/loggerd_tests_common.py @@ -10,7 +10,7 @@ from openpilot.system.loggerd.xattr_cache import setxattr -def create_random_file(file_path: Path, size_mb: float, lock: bool = False, upload_xattr: bytes = None) -> None: +def create_random_file(file_path: Path, size_mb: float, lock: bool = False, upload_xattr: bytes | None = None) -> None: file_path.parent.mkdir(parents=True, exist_ok=True) if lock: @@ -63,10 +63,10 @@ class UploaderTestCase: seg_dir: str def set_ignore(self): - uploader.Api = MockApiIgnore + uploader.Api = MockApiIgnore # type: ignore[assignment] # monkeypatch for testing def setup_method(self): - uploader.Api = MockApi + uploader.Api = MockApi # type: ignore[assignment] # monkeypatch for testing uploader.fake_upload = True uploader.force_wifi = True uploader.allow_sleep = False @@ -80,7 +80,7 @@ def setup_method(self): self.params.put("DongleId", "0000000000000000") def make_file_with_data(self, f_dir: str, fn: str, size_mb: float = .1, lock: bool = False, - upload_xattr: bytes = None, preserve_xattr: bytes = None) -> Path: + upload_xattr: bytes | None = None, preserve_xattr: bytes | None = None) -> Path: file_path = Path(Paths.log_root()) / f_dir / fn create_random_file(file_path, size_mb, lock, upload_xattr) diff --git a/system/loggerd/tests/test_deleter.py b/system/loggerd/tests/test_deleter.py index 6222ea253ba0db..460d4d8bc5bd67 100644 --- a/system/loggerd/tests/test_deleter.py +++ b/system/loggerd/tests/test_deleter.py @@ -19,7 +19,7 @@ def setup_method(self): self.f_type = "fcamera.hevc" super().setup_method() self.fake_stats = Stats(f_bavail=0, f_blocks=10, f_frsize=4096) - deleter.os.statvfs = self.fake_statvfs + deleter.os.statvfs = self.fake_statvfs # type: ignore[assignment] # monkeypatch for testing def start_thread(self): self.end_event = threading.Event() diff --git a/system/loggerd/tests/test_loggerd.py b/system/loggerd/tests/test_loggerd.py index 1cac16adcd4e06..63445d2014a9c4 100644 --- a/system/loggerd/tests/test_loggerd.py +++ b/system/loggerd/tests/test_loggerd.py @@ -266,7 +266,9 @@ def test_qlog(self): assert recv_cnt == 0, f"got {recv_cnt} {s} msgs in qlog" else: # check logged message count matches decimation - expected_cnt = (len(msgs) - 1) // SERVICE_LIST[s].decimation + 1 + decimation = SERVICE_LIST[s].decimation + assert decimation is not None + expected_cnt = (len(msgs) - 1) // decimation + 1 assert recv_cnt == expected_cnt, f"expected {expected_cnt} msgs for {s}, got {recv_cnt}" def test_rlog(self): diff --git a/system/loggerd/tests/test_uploader.py b/system/loggerd/tests/test_uploader.py index 961a8aa36f8cae..562bc068eb80e8 100644 --- a/system/loggerd/tests/test_uploader.py +++ b/system/loggerd/tests/test_uploader.py @@ -50,7 +50,7 @@ def join_thread(self): self.end_event.set() self.up_thread.join() - def gen_files(self, lock=False, xattr: bytes = None, boot=True) -> list[Path]: + def gen_files(self, lock=False, xattr: bytes | None = None, boot=True) -> list[Path]: f_paths = [] for t in ["qlog", "rlog", "dcamera.hevc", "fcamera.hevc"]: f_paths.append(self.make_file_with_data(self.seg_dir, t, 1, lock=lock, upload_xattr=xattr)) diff --git a/system/loggerd/uploader.py b/system/loggerd/uploader.py index 5b6234e1d55f35..8ac38b6df6a76d 100755 --- a/system/loggerd/uploader.py +++ b/system/loggerd/uploader.py @@ -226,7 +226,7 @@ def step(self, network_type: int, metered: bool) -> bool | None: return self.upload(name, key, fn, network_type, metered) -def main(exit_event: threading.Event = None) -> None: +def main(exit_event: threading.Event | None = None) -> None: if exit_event is None: exit_event = threading.Event() diff --git a/system/manager/build.py b/system/manager/build.py index d79e7fd2ad906e..4462c0e55e7915 100755 --- a/system/manager/build.py +++ b/system/manager/build.py @@ -2,6 +2,7 @@ import os import subprocess from pathlib import Path +from typing import cast # NOTE: Do NOT import anything here that needs be built (e.g. params) from openpilot.common.basedir import BASEDIR @@ -62,7 +63,9 @@ def build(spinner: Spinner, dirty: bool = False, minimal: bool = False) -> None: if scons.returncode != 0: # Read remaining output if scons.stderr is not None: - compile_output += scons.stderr.read().split(b'\n') + # ty bug: Popen without text=True returns bytes, but ty infers str + remaining = cast(bytes, scons.stderr.read()) + compile_output += remaining.split(b'\n') # Build failed log errors error_s = b"\n".join(compile_output).decode('utf8', 'replace') diff --git a/system/manager/process.py b/system/manager/process.py index 1e24198267b823..36e1ba77b286dd 100644 --- a/system/manager/process.py +++ b/system/manager/process.py @@ -81,7 +81,7 @@ def restart(self) -> None: self.stop(sig=signal.SIGKILL) self.start() - def stop(self, retry: bool = True, block: bool = True, sig: signal.Signals = None) -> int | None: + def stop(self, retry: bool = True, block: bool = True, sig: signal.Signals | None = None) -> int | None: if self.proc is None: return None diff --git a/system/qcomgpsd/nmeaport.py b/system/qcomgpsd/nmeaport.py index 8b9ab510864e98..78de5cf3eaba62 100644 --- a/system/qcomgpsd/nmeaport.py +++ b/system/qcomgpsd/nmeaport.py @@ -30,7 +30,8 @@ class GnssClockNmeaPort: def __post_init__(self): for field in fields(self): val = getattr(self, field.name) - setattr(self, field.name, field.type(val) if val else None) + if val and callable(field.type): + setattr(self, field.name, field.type(val)) @dataclass class GnssMeasNmeaPort: @@ -73,7 +74,8 @@ class GnssMeasNmeaPort: def __post_init__(self): for field in fields(self): val = getattr(self, field.name) - setattr(self, field.name, field.type(val) if val else None) + if val and callable(field.type): + setattr(self, field.name, field.type(val)) def nmea_checksum_ok(s): checksum = 0 @@ -107,11 +109,11 @@ def process_nmea_port_messages(device:str="/dev/ttyUSB1") -> NoReturn: match fields[0]: case "$GNCLK": # fields at end are reserved (not used) - gnss_clock = GnssClockNmeaPort(*fields[1:10]) # type: ignore[arg-type] + gnss_clock = GnssClockNmeaPort(*fields[1:10]) print(gnss_clock) case "$GNMEAS": # fields at end are reserved (not used) - gnss_meas = GnssMeasNmeaPort(*fields[1:14]) # type: ignore[arg-type] + gnss_meas = GnssMeasNmeaPort(*fields[1:14]) print(gnss_meas) except Exception as e: print(e) diff --git a/system/statsd.py b/system/statsd.py index 33e9e9912d4a1c..7e8808024b7883 100755 --- a/system/statsd.py +++ b/system/statsd.py @@ -177,7 +177,7 @@ def get_influxdb_line(measurement: str, value: float | dict[str, float], timest ctx.term() +statlog = StatLog() + if __name__ == "__main__": main() -else: - statlog = StatLog() diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 26e446612d34b9..5807053fae6241 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -184,7 +184,8 @@ def _handle_mouse_event(self): time.monotonic(), ) # Only add changes - if self._prev_mouse_event[slot] is None or ev[:-1] != self._prev_mouse_event[slot][:-1]: + prev = self._prev_mouse_event[slot] + if prev is None or ev[:-1] != prev[:-1]: with self._lock: self._events.append(ev) self._prev_mouse_event[slot] = ev @@ -339,8 +340,9 @@ def _startup_profile_context(self): def set_modal_overlay(self, overlay, callback: Callable | None = None): if self._modal_overlay.overlay is not None: - if hasattr(self._modal_overlay.overlay, 'hide_event'): - self._modal_overlay.overlay.hide_event() + hide_event = getattr(self._modal_overlay.overlay, 'hide_event', None) + if hide_event is not None: + hide_event() if self._modal_overlay.callback is not None: self._modal_overlay.callback(-1) @@ -544,24 +546,27 @@ def height(self): def _handle_modal_overlay(self) -> bool: if self._modal_overlay.overlay: - if hasattr(self._modal_overlay.overlay, 'render'): - result = self._modal_overlay.overlay.render(rl.Rectangle(0, 0, self.width, self.height)) + render_fn = getattr(self._modal_overlay.overlay, 'render', None) + if render_fn is not None: + result = render_fn(rl.Rectangle(0, 0, self.width, self.height)) elif callable(self._modal_overlay.overlay): result = self._modal_overlay.overlay() else: raise Exception # Send show event to Widget - if not self._modal_overlay_shown and hasattr(self._modal_overlay.overlay, 'show_event'): - self._modal_overlay.overlay.show_event() + show_event = getattr(self._modal_overlay.overlay, 'show_event', None) + if not self._modal_overlay_shown and show_event is not None: + show_event() self._modal_overlay_shown = True if result >= 0: # Clear the overlay and execute the callback original_modal = self._modal_overlay self._modal_overlay = ModalOverlay() - if hasattr(original_modal.overlay, 'hide_event'): - original_modal.overlay.hide_event() + hide_event = getattr(original_modal.overlay, 'hide_event', None) + if hide_event is not None: + hide_event() if original_modal.callback is not None: original_modal.callback(result) return True @@ -595,7 +600,7 @@ def _draw_text_ex_scaled(font, text, position, font_size, spacing, tint): font = font_fallback(font) return rl._orig_draw_text_ex(font, text, position, font_size * FONT_SCALE, spacing, tint) - rl.draw_text_ex = _draw_text_ex_scaled + rl.draw_text_ex = _draw_text_ex_scaled # type: ignore[assignment] # monkeypatch pyray def _set_log_callback(self): ffi_libc = cffi.FFI() @@ -691,6 +696,7 @@ def _output_render_profile(self): import pstats self._render_profiler.disable() + assert self._render_profile_start_time is not None elapsed_ms = (time.monotonic() - self._render_profile_start_time) * 1e3 avg_frame_time = elapsed_ms / self._frame if self._frame > 0 else 0 diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 9792c51e7c25a0..790273f599f7c0 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -173,7 +173,7 @@ def set_title(self, text: str): def set_icon(self, icon_texture: rl.Texture): self._icon_texture = icon_texture - def _render(self, _): + def _render(self, rect): rl.draw_texture_ex(self._icon_texture, rl.Vector2(self._rect.x, self._rect.y), 0.0, 1.0, rl.WHITE) @@ -243,7 +243,7 @@ def _scrolled_down_offset(self): def _render_content(self, scroll_offset): pass - def _render(self, _): + def _render(self, rect): scroll_offset = round(self._scroll_panel.update(self._rect, self._content_height + self._continue_button.rect.height + 16)) if scroll_offset <= self._scrolled_down_offset: @@ -466,7 +466,7 @@ def hide_event(self): super().hide_event() self._wifi_ui.hide_event() - def _render(self, _): + def _render(self, rect): if self._state == NetworkSetupState.MAIN: self._network_header.render(rl.Rectangle( self._rect.x + 16, diff --git a/system/ui/mici_updater.py b/system/ui/mici_updater.py index 2ae2f7cc192032..67954ecc22149b 100755 --- a/system/ui/mici_updater.py +++ b/system/ui/mici_updater.py @@ -101,6 +101,7 @@ def _run_update_process(self): cmd = [self.updater, "--swap", self.manifest] self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1, universal_newlines=True) + assert self.process.stdout is not None for line in self.process.stdout: parts = line.strip().split(":") diff --git a/system/ui/tici_updater.py b/system/ui/tici_updater.py index 2e1a8687e1a822..e1b6e38472e160 100755 --- a/system/ui/tici_updater.py +++ b/system/ui/tici_updater.py @@ -70,6 +70,7 @@ def _run_update_process(self): cmd = [self.updater, "--swap", self.manifest] self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1, universal_newlines=True) + assert self.process.stdout is not None for line in self.process.stdout: parts = line.strip().split(":") diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index a3fed6d96217fa..9e2731d29dca34 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -8,9 +8,10 @@ try: from openpilot.selfdrive.ui.ui_state import device except ImportError: - class Device: + # Fallback when ui_state not available (standalone UI apps) + class _FallbackDevice: awake = True - device = Device() # type: ignore + device = _FallbackDevice() # type: ignore[assignment] # ty limitation: conditional import fallback class DialogResult(IntEnum): @@ -91,7 +92,7 @@ def _hit_rect(self) -> rl.Rectangle: return self._rect return rl.get_collision_rec(self._rect, self._parent_rect) - def render(self, rect: rl.Rectangle = None) -> bool | int | None: + def render(self, rect: rl.Rectangle | None = None) -> bool | int | None: if rect is not None: self.set_rect(rect) @@ -216,7 +217,7 @@ def show_event(self): self._alpha_filter.x = 1.0 self._fade_time = rl.get_time() - def _render(self, _): + def _render(self, rect): if rl.get_time() - self._fade_time > DISMISS_TIME_SECONDS: self._alpha = 0.0 alpha = self._alpha_filter.update(self._alpha) @@ -299,7 +300,7 @@ def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: # block horizontal swiping if now swiping away if self._can_swipe_away: - if mouse_event.pos.y - self._back_button_start_pos.y > START_DISMISSING_THRESHOLD: # type: ignore + if mouse_event.pos.y - self._back_button_start_pos.y > START_DISMISSING_THRESHOLD: self._swiping_away = True elif mouse_event.left_released: @@ -361,7 +362,7 @@ def _update_state(self): self.set_position(self._rect.x, new_y) - def render(self, rect: rl.Rectangle = None) -> bool | int | None: + def render(self, rect: rl.Rectangle | None = None) -> bool | int | None: ret = super().render(rect) if self.back_enabled: diff --git a/system/ui/widgets/button.py b/system/ui/widgets/button.py index 34b2a51a426ec5..150221e4a8f861 100644 --- a/system/ui/widgets/button.py +++ b/system/ui/widgets/button.py @@ -123,7 +123,7 @@ def _update_state(self): self._background_color = BUTTON_DISABLED_BACKGROUND_COLORS.get(self._button_style, rl.Color(51, 51, 51, 255)) self._label.set_text_color(BUTTON_DISABLED_TEXT_COLORS.get(self._button_style, rl.Color(228, 228, 228, 51))) - def _render(self, _): + def _render(self, rect): roundness = self._border_radius / (min(self._rect.width, self._rect.height) / 2) if self._button_style == ButtonStyle.TRANSPARENT_WHITE_BORDER: rl.draw_rectangle_rounded(self._rect, roundness, 10, rl.BLACK) @@ -161,7 +161,7 @@ def _update_state(self): else: self._background_color = BUTTON_BACKGROUND_COLORS[ButtonStyle.NORMAL] - def _render(self, _): + def _render(self, rect): roundness = self._border_radius / (min(self._rect.width, self._rect.height) / 2) rl.draw_rectangle_rounded(self._rect, roundness, 10, self._background_color) self._label.render(self._rect) @@ -209,7 +209,7 @@ def set_opacity(self, opacity: float, smooth: bool = False): else: self._opacity_filter.x = opacity - def _render(self, _): + def _render(self, rect): bg_txt = self._icon_bg_pressed_txt if self.is_pressed else self._icon_bg_txt white = rl.Color(255, 255, 255, int(255 * self._opacity_filter.x)) rl.draw_texture(bg_txt, int(self.rect.x), int(self.rect.y), white) @@ -246,7 +246,7 @@ def set_opacity(self, opacity: float, smooth: bool = False): else: self._opacity_filter.x = opacity - def _render(self, _): + def _render(self, rect): if not self.enabled and self._bg_disabled_txt is not None: rl.draw_texture(self._bg_disabled_txt, int(self.rect.x), int(self.rect.y), rl.Color(255, 255, 255, int(255 * self._opacity_filter.x))) elif self.is_pressed: diff --git a/system/ui/widgets/label.py b/system/ui/widgets/label.py index 97b293083d0080..86c85a45282d30 100644 --- a/system/ui/widgets/label.py +++ b/system/ui/widgets/label.py @@ -31,13 +31,13 @@ class MiciLabel(Widget): def __init__(self, text: str, font_size: int = DEFAULT_TEXT_SIZE, - width: int = None, + width: int | None = None, color: rl.Color = DEFAULT_TEXT_COLOR, font_weight: FontWeight = FontWeight.NORMAL, alignment: int = rl.GuiTextAlignment.TEXT_ALIGN_LEFT, alignment_vertical: int = rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP, spacing: int = 0, - line_height: int = None, + line_height: int | None = None, elide_right: bool = True, wrap_text: bool = False, scroll: bool = False): @@ -338,7 +338,7 @@ def _update_text(self, text): self._emojis.append(find_emoji(t)) self._text_size.append(measure_text_cached(self._font, t, self._font_size)) - def _render(self, _): + def _render(self, rect): # Text can be a callable # TODO: cache until text changed self._update_text(self._text) @@ -627,7 +627,7 @@ def get_content_height(self, max_width: int) -> float: return self._cached_total_height return 0.0 - def _render(self, _): + def _render(self, rect): """Render the label.""" if self._rect.width <= 0 or self._rect.height <= 0: return diff --git a/system/ui/widgets/list_view.py b/system/ui/widgets/list_view.py index cfb8ab58a356b4..b4b5479c7b8fcd 100644 --- a/system/ui/widgets/list_view.py +++ b/system/ui/widgets/list_view.py @@ -174,8 +174,8 @@ def set_text(self, text: str | Callable[[], str]): class DualButtonAction(ItemAction): - def __init__(self, left_text: str | Callable[[], str], right_text: str | Callable[[], str], left_callback: Callable = None, - right_callback: Callable = None, enabled: bool | Callable[[], bool] = True): + def __init__(self, left_text: str | Callable[[], str], right_text: str | Callable[[], str], left_callback: Callable | None = None, + right_callback: Callable | None = None, enabled: bool | Callable[[], bool] = True): super().__init__(width=0, enabled=enabled) # Width 0 means use full width self.left_button = Button(left_text, click_callback=left_callback, button_style=ButtonStyle.NORMAL, text_padding=0) self.right_button = Button(right_text, click_callback=right_callback, button_style=ButtonStyle.DANGER, text_padding=0) @@ -207,7 +207,7 @@ def _render(self, rect: rl.Rectangle): class MultipleButtonAction(ItemAction): - def __init__(self, buttons: list[str | Callable[[], str]], button_width: int, selected_index: int = 0, callback: Callable = None): + def __init__(self, buttons: list[str | Callable[[], str]], button_width: int, selected_index: int = 0, callback: Callable | None = None): super().__init__(width=len(buttons) * button_width + (len(buttons) - 1) * RIGHT_ITEM_PADDING, enabled=True) self.buttons = buttons self.button_width = button_width @@ -338,7 +338,7 @@ def _update_state(self): if new_description != self._prev_description: self._parse_description(new_description) - def _render(self, _): + def _render(self, rect): if not self.is_visible: return @@ -454,13 +454,14 @@ def text_item(title: str | Callable[[], str], value: str | Callable[[], str], de return ListItem(title=title, description=description, action_item=action, callback=callback) -def dual_button_item(left_text: str | Callable[[], str], right_text: str | Callable[[], str], left_callback: Callable = None, right_callback: Callable = None, - description: str | Callable[[], str] | None = None, enabled: bool | Callable[[], bool] = True) -> ListItem: +def dual_button_item(left_text: str | Callable[[], str], right_text: str | Callable[[], str], left_callback: Callable | None = None, + right_callback: Callable | None = None, description: str | Callable[[], str] | None = None, + enabled: bool | Callable[[], bool] = True) -> ListItem: action = DualButtonAction(left_text, right_text, left_callback, right_callback, enabled) return ListItem(title="", description=description, action_item=action) def multiple_button_item(title: str | Callable[[], str], description: str | Callable[[], str], buttons: list[str | Callable[[], str]], selected_index: int, - button_width: int = BUTTON_WIDTH, callback: Callable = None, icon: str = ""): + button_width: int = BUTTON_WIDTH, callback: Callable | None = None, icon: str = ""): action = MultipleButtonAction(buttons, button_width, selected_index, callback=callback) return ListItem(title=title, description=description, icon=icon, action_item=action) diff --git a/system/ui/widgets/mici_keyboard.py b/system/ui/widgets/mici_keyboard.py index 7459dc57317b7f..5167614f3210bc 100644 --- a/system/ui/widgets/mici_keyboard.py +++ b/system/ui/widgets/mici_keyboard.py @@ -77,7 +77,7 @@ def get_position(self) -> tuple[float, float]: def _update_state(self): self._color.a = min(int(255 * self._alpha_filter.x), 255) - def _render(self, _): + def _render(self, rect): # center char at rect position text_size = measure_text_cached(self._font, self.char, self._get_font_size()) x = self._rect.x + self._rect.width / 2 - text_size.x / 2 @@ -113,7 +113,7 @@ def __init__(self, icon: str, vertical_align: str = "center", char: str = ""): def set_icon(self, icon: str): self._icon = gui_app.texture(icon, 38, 38) - def _render(self, _): + def _render(self, rect): scale = np.interp(self._size_filter.x, [CHAR_FONT_SIZE, CHAR_NEAR_FONT_SIZE], [1, 1.5]) if self._vertical_align == "center": @@ -367,7 +367,7 @@ def _lay_out_keys(self, bg_x, bg_y, keys: list[list[Key]]): # TODO: I like the push amount, so we should clip the pos inside the keyboard rect key.set_position(key_x, key_y) - def _render(self, _): + def _render(self, rect): # draw bg bg_x = self._rect.x + (self._rect.width - self._txt_bg.width) / 2 bg_y = self._rect.y + self._rect.height - self._txt_bg.height diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index f41a04c2491f8d..c1c2a9c64e1975 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -21,9 +21,10 @@ from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.lib.prime_state import PrimeType except Exception: + # Fallback for standalone apps - ty limitation: conditional import fallback Params = None - ui_state = None # type: ignore - PrimeType = None # type: ignore + ui_state = None # type: ignore[assignment] + PrimeType = None # type: ignore[assignment] NM_DEVICE_STATE_NEED_AUTH = 60 MIN_PASSWORD_LENGTH = 8 @@ -58,7 +59,7 @@ def __init__(self, text: str): self.text = text self.set_rect(rl.Rectangle(0, 0, 400, 100)) - def _render(self, _): + def _render(self, rect): color = rl.Color(74, 74, 74, 255) if self.is_pressed else rl.Color(57, 57, 57, 255) rl.draw_rectangle_rounded(self._rect, 0.6, 10, color) gui_label(self.rect, self.text, font_size=60, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) @@ -87,7 +88,7 @@ def _cycle_panel(self): else: self._set_current_panel(PanelType.WIFI) - def _render(self, _): + def _render(self, rect): # subtract button content_rect = rl.Rectangle(self._rect.x, self._rect.y + self._nav_button.rect.height + 40, self._rect.width, self._rect.height - self._nav_button.rect.height - 40) @@ -111,6 +112,7 @@ def __init__(self, wifi_manager: WifiManager): super().__init__() self._wifi_manager = wifi_manager self._wifi_manager.add_callbacks(networks_updated=self._on_network_updated) + assert Params is not None self._params = Params() self._keyboard = Keyboard(max_text_size=MAX_PASSWORD_LENGTH, min_text_size=MIN_PASSWORD_LENGTH, show_password_toggle=True) @@ -265,7 +267,7 @@ def _update_state(self): self._apn_btn.set_visible(show_cell_settings) self._cellular_metered_btn.set_visible(show_cell_settings) - def _render(self, _): + def _render(self, rect): self._scroller.render(self._rect) diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index f33ba941bf9190..b1d36cb06230ed 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -27,7 +27,7 @@ def set_parent_rect(self, parent_rect: rl.Rectangle) -> None: super().set_parent_rect(parent_rect) self._rect.width = parent_rect.width - def _render(self, _): + def _render(self, rect): rl.draw_line(int(self._rect.x) + LINE_PADDING, int(self._rect.y), int(self._rect.x + self._rect.width) - LINE_PADDING, int(self._rect.y), LINE_COLOR) @@ -223,7 +223,7 @@ def _layout(self): item.set_position(round(x), round(y)) # round to prevent jumping when settling item.set_parent_rect(self._rect) - def _render(self, _): + def _render(self, rect): for item in self._visible_items: # Skip rendering if not in viewport if not rl.check_collision_recs(item.rect, self._rect): diff --git a/system/ui/widgets/scroller_tici.py b/system/ui/widgets/scroller_tici.py index a843010d56bac6..ef17ba67f8cba4 100644 --- a/system/ui/widgets/scroller_tici.py +++ b/system/ui/widgets/scroller_tici.py @@ -16,7 +16,7 @@ def set_parent_rect(self, parent_rect: rl.Rectangle) -> None: super().set_parent_rect(parent_rect) self._rect.width = parent_rect.width - def _render(self, _): + def _render(self, rect): rl.draw_line(int(self._rect.x) + LINE_PADDING, int(self._rect.y), int(self._rect.x + self._rect.width) - LINE_PADDING, int(self._rect.y), LINE_COLOR) @@ -39,7 +39,7 @@ def add_widget(self, item: Widget) -> None: self._items.append(item) item.set_touch_valid_callback(self.scroll_panel.is_touch_valid) - def _render(self, _): + def _render(self, rect): # TODO: don't draw items that are not in the viewport visible_items = [item for item in self._items if item.is_visible] diff --git a/system/ui/widgets/slider.py b/system/ui/widgets/slider.py index b17d8f3b7cd506..9990614aa10463 100644 --- a/system/ui/widgets/slider.py +++ b/system/ui/widgets/slider.py @@ -114,7 +114,7 @@ def _update_state(self): # not activated yet, keep movement 1:1 self._scroll_x_circle_filter.x = self._scroll_x_circle - def _render(self, _): + def _render(self, rect): # TODO: iOS text shimmering animation white = rl.Color(255, 255, 255, int(255 * self._opacity)) diff --git a/system/updated/casync/casync.py b/system/updated/casync/casync.py index 79ac26f1c6e03e..2bd46a1ffba45c 100755 --- a/system/updated/casync/casync.py +++ b/system/updated/casync/casync.py @@ -168,7 +168,7 @@ def build_chunk_dict(chunks: list[Chunk]) -> ChunkDict: def extract(target: list[Chunk], sources: list[tuple[str, ChunkReader, ChunkDict]], out_path: str, - progress: Callable[[int], None] = None): + progress: Callable[[int], None] | None = None): stats: dict[str, int] = defaultdict(int) mode = 'rb+' if os.path.exists(out_path) else 'wb' @@ -208,7 +208,7 @@ def extract_directory(target: list[Chunk], sources: list[tuple[str, ChunkReader, ChunkDict]], out_path: str, tmp_file: str, - progress: Callable[[int], None] = None): + progress: Callable[[int], None] | None = None): """extract a directory stored as a casync tar archive""" stats = extract(target, sources, tmp_file, progress) diff --git a/system/updated/updated.py b/system/updated/updated.py index a4a1f8f34f8194..ffd10e038db79d 100755 --- a/system/updated/updated.py +++ b/system/updated/updated.py @@ -68,7 +68,7 @@ def write_time_to_param(params, param) -> None: t = datetime.datetime.now(datetime.UTC).replace(tzinfo=None) params.put(param, t) -def run(cmd: list[str], cwd: str = None) -> str: +def run(cmd: list[str], cwd: str | None = None) -> str: return subprocess.check_output(cmd, cwd=cwd, stderr=subprocess.STDOUT, encoding='utf8') diff --git a/system/webrtc/device/audio.py b/system/webrtc/device/audio.py index 4b22033e03e639..b1859518a17b37 100644 --- a/system/webrtc/device/audio.py +++ b/system/webrtc/device/audio.py @@ -16,7 +16,7 @@ class AudioInputStreamTrack(aiortc.mediastreams.AudioStreamTrack): pyaudio.paFloat32: 'flt', } - def __init__(self, audio_format: int = pyaudio.paInt16, rate: int = 16000, channels: int = 1, packet_time: float = 0.020, device_index: int = None): + def __init__(self, audio_format: int = pyaudio.paInt16, rate: int = 16000, channels: int = 1, packet_time: float = 0.020, device_index: int | None = None): super().__init__() self.p = pyaudio.PyAudio() @@ -48,7 +48,7 @@ async def recv(self): class AudioOutputSpeaker: - def __init__(self, audio_format: int = pyaudio.paInt16, rate: int = 48000, channels: int = 2, packet_time: float = 0.2, device_index: int = None): + def __init__(self, audio_format: int = pyaudio.paInt16, rate: int = 48000, channels: int = 2, packet_time: float = 0.2, device_index: int | None = None): chunk_size = int(packet_time * rate) self.p = pyaudio.PyAudio() diff --git a/tools/jotpluggler/data.py b/tools/jotpluggler/data.py index cf27857d1f117c..756b87bd20c68c 100644 --- a/tools/jotpluggler/data.py +++ b/tools/jotpluggler/data.py @@ -9,7 +9,7 @@ from openpilot.tools.lib.logreader import _LogFileReader, LogReader -def flatten_dict(d: dict, sep: str = "/", prefix: str = None) -> dict: +def flatten_dict(d: dict, sep: str = "/", prefix: str | None = None) -> dict: result = {} stack: list[tuple] = [(d, prefix)] diff --git a/tools/jotpluggler/pluggle.py b/tools/jotpluggler/pluggle.py index 879b677514ba4f..6fb8d5049bd1de 100755 --- a/tools/jotpluggler/pluggle.py +++ b/tools/jotpluggler/pluggle.py @@ -7,7 +7,7 @@ import multiprocessing import uuid import signal -import yaml # type: ignore +import yaml from openpilot.common.swaglog import cloudlog from openpilot.common.basedir import BASEDIR from openpilot.tools.jotpluggler.data import DataManager diff --git a/tools/jotpluggler/views.py b/tools/jotpluggler/views.py index 1c4d9a8f3c0513..e82e148f5fb48a 100644 --- a/tools/jotpluggler/views.py +++ b/tools/jotpluggler/views.py @@ -9,7 +9,7 @@ class ViewPanel(ABC): """Abstract base class for all view panels that can be displayed in a plot container""" - def __init__(self, panel_id: str = None): + def __init__(self, panel_id: str | None = None): self.panel_id = panel_id or str(uuid.uuid4()) self.title = "Untitled Panel" @@ -60,8 +60,8 @@ def __init__(self, data_manager, playback_manager, worker_manager, panel_id: str self._update_lock = threading.RLock() self._results_deque: deque[tuple[str, list, list]] = deque() self._new_data = False - self._last_x_limits = (0.0, 0.0) - self._queued_x_sync: tuple | None = None + self._last_x_limits: tuple[float, float] = (0.0, 0.0) + self._queued_x_sync: tuple[float, float] | None = None self._queued_reallow_x_zoom = False self._total_segments = self.playback_manager.num_segments @@ -124,15 +124,16 @@ def update(self): self.add_series(series_path, update=True) current_limits = dpg.get_axis_limits(self.x_axis_tag) + current_limits_tuple: tuple[float, float] = (float(current_limits[0]), float(current_limits[1])) # downsample if plot zoom changed significantly - plot_duration = current_limits[1] - current_limits[0] + plot_duration = current_limits_tuple[1] - current_limits_tuple[0] if plot_duration > self._last_plot_duration * 2 or plot_duration < self._last_plot_duration * 0.5: self._downsample_all_series(plot_duration) # sync x-axis if changed by user - if self._last_x_limits != current_limits: - self.playback_manager.set_x_axis_bounds(current_limits[0], current_limits[1], source_panel=self) - self._last_x_limits = current_limits - self._fit_y_axis(current_limits[0], current_limits[1]) + if self._last_x_limits != current_limits_tuple: + self.playback_manager.set_x_axis_bounds(current_limits_tuple[0], current_limits_tuple[1], source_panel=self) + self._last_x_limits = current_limits_tuple + self._fit_y_axis(current_limits_tuple[0], current_limits_tuple[1]) while self._results_deque: # handle downsampled results in main thread results = self._results_deque.popleft() diff --git a/tools/joystick/joystick_control.py b/tools/joystick/joystick_control.py index 11d17e587e8451..9533294cfd6cbb 100755 --- a/tools/joystick/joystick_control.py +++ b/tools/joystick/joystick_control.py @@ -85,7 +85,7 @@ def update(self): norm = -float(np.interp(event[1], [self.min_axis_value[event[0]], self.max_axis_value[event[0]]], [-1., 1.])) norm = norm if abs(norm) > 0.03 else 0. # center can be noisy, deadzone of 3% - self.axes_values[event[0]] = EXPO * norm ** 3 + (1 - EXPO) * norm # less action near center for fine control + self.axes_values[event[0]] = float(EXPO * norm ** 3 + (1 - EXPO) * norm) # less action near center for fine control else: return False return True diff --git a/tools/lib/comma_car_segments.py b/tools/lib/comma_car_segments.py index cd19356d6627de..b27887ed29c98d 100644 --- a/tools/lib/comma_car_segments.py +++ b/tools/lib/comma_car_segments.py @@ -74,7 +74,7 @@ def get_repo_url(path): response = requests.head(get_repo_raw_url(path)) - if "text/plain" in response.headers.get("content-type"): + if "text/plain" in response.headers.get("content-type", ""): # This is an LFS pointer, so download the raw data from lfs response = requests.get(get_repo_raw_url(path)) assert response.status_code == 200 diff --git a/tools/lib/logreader.py b/tools/lib/logreader.py index f9a90490b91f3c..9696c8524d104d 100755 --- a/tools/lib/logreader.py +++ b/tools/lib/logreader.py @@ -13,7 +13,6 @@ import zstandard as zstd from collections.abc import Iterable, Iterator -from typing import cast from urllib.parse import parse_qs, urlparse from cereal import log as capnp_log @@ -180,7 +179,7 @@ def auto_source(identifier: str, sources: list[Source], default_mode: ReadMode) # We've found all files, return them if len(needed_seg_idxs) == 0: - return cast(list[str], list(valid_files.values())) + return list(valid_files.values()) else: raise FileNotFoundError(f"Did not find {fn} for seg idxs {needed_seg_idxs} of {sr.route_name}") @@ -245,7 +244,7 @@ def _parse_identifier(self, identifier: str) -> list[str]: return identifiers def __init__(self, identifier: str | list[str], default_mode: ReadMode = ReadMode.RLOG, - sources: list[Source] = None, sort_by_time=False, only_union_types=False): + sources: list[Source] | None = None, sort_by_time=False, only_union_types=False): if sources is None: sources = [internal_source, comma_api_source, openpilotci_source, comma_car_segments_source] diff --git a/tools/lib/vidindex.py b/tools/lib/vidindex.py index f2e4e9ca45e3e9..f2027f52fce87f 100755 --- a/tools/lib/vidindex.py +++ b/tools/lib/vidindex.py @@ -140,7 +140,7 @@ def get_ue(dat: bytes, start_idx: int, skip_bits: int) -> tuple[int, int]: j -= 1 if prefix_val == 1 and prefix_len - 1 == suffix_len: - val = 2**(prefix_len-1) - 1 + suffix_val + val = (1 << (prefix_len - 1)) - 1 + suffix_val size = prefix_len + suffix_len return val, size i += 1 diff --git a/tools/sim/bridge/metadrive/metadrive_bridge.py b/tools/sim/bridge/metadrive/metadrive_bridge.py index b8dc94cf86fc75..abaad48cc79987 100644 --- a/tools/sim/bridge/metadrive/metadrive_bridge.py +++ b/tools/sim/bridge/metadrive/metadrive_bridge.py @@ -57,7 +57,7 @@ def __init__(self, dual_camera, high_quality, test_duration=math.inf, test_run=F self.test_run = test_run self.test_duration = test_duration if self.test_run else math.inf - def spawn_world(self, queue: Queue): + def spawn_world(self, q: Queue): sensors = { "rgb_road": (RGBCameraRoad, W, H, ) } @@ -90,4 +90,4 @@ def spawn_world(self, queue: Queue): anisotropic_filtering=False ) - return MetaDriveWorld(queue, config, self.test_duration, self.test_run, self.dual_camera) + return MetaDriveWorld(q, config, self.test_duration, self.test_run, self.dual_camera) diff --git a/tools/sim/bridge/metadrive/metadrive_process.py b/tools/sim/bridge/metadrive/metadrive_process.py index 2486d87ff997cb..8cb99582089702 100644 --- a/tools/sim/bridge/metadrive/metadrive_process.py +++ b/tools/sim/bridge/metadrive/metadrive_process.py @@ -33,20 +33,20 @@ def add_image_sensor_patched(self, name: str, cls, args): assert isinstance(sensor, ImageBuffer), "This API is for adding image sensor" self.sensors[name] = sensor - EngineCore.add_image_sensor = add_image_sensor_patched + EngineCore.add_image_sensor = add_image_sensor_patched # type: ignore[assignment] # monkeypatch # we aren't going to use the built-in observation stack, so disable it to save time def observe_patched(self, *args, **kwargs): return self.state - ImageObservation.observe = observe_patched + ImageObservation.observe = observe_patched # type: ignore[assignment] # monkeypatch # disable destination, we want to loop forever def arrive_destination_patch(self, *args, **kwargs): return False if not arrive_dest_done: - MetaDriveEnv._is_arrive_destination = arrive_destination_patch + MetaDriveEnv._is_arrive_destination = arrive_destination_patch # type: ignore[assignment] # monkeypatch def metadrive_process(dual_camera: bool, config: dict, camera_array, wide_camera_array, image_lock, controls_recv: Connection, simulation_state_send: Connection, vehicle_state_send: Connection, diff --git a/tools/sim/bridge/metadrive/metadrive_world.py b/tools/sim/bridge/metadrive/metadrive_world.py index c5111289d01394..0d71dedcc05692 100644 --- a/tools/sim/bridge/metadrive/metadrive_world.py +++ b/tools/sim/bridge/metadrive/metadrive_world.py @@ -18,11 +18,11 @@ def __init__(self, status_q, config, test_duration, test_run, dual_camera=False) super().__init__(dual_camera) self.status_q = status_q self.camera_array = Array(ctypes.c_uint8, W*H*3) - self.road_image = np.frombuffer(self.camera_array.get_obj(), dtype=np.uint8).reshape((H, W, 3)) + self.road_image = np.frombuffer(memoryview(self.camera_array.get_obj()), dtype=np.uint8).reshape((H, W, 3)) self.wide_camera_array = None if dual_camera: self.wide_camera_array = Array(ctypes.c_uint8, W*H*3) - self.wide_road_image = np.frombuffer(self.wide_camera_array.get_obj(), dtype=np.uint8).reshape((H, W, 3)) + self.wide_road_image = np.frombuffer(memoryview(self.wide_camera_array.get_obj()), dtype=np.uint8).reshape((H, W, 3)) self.controls_send, self.controls_recv = Pipe() self.simulation_state_send, self.simulation_state_recv = Pipe() @@ -58,9 +58,9 @@ def __init__(self, status_q, config, test_duration, test_run, dual_camera=False) self.reset_time = 0 self.should_reset = False - def apply_controls(self, steer_angle, throttle_out, brake_out): + def apply_controls(self, steer_sim, throttle_out, brake_out): if (time.monotonic() - self.reset_time) > 2: - self.vc[0] = steer_angle + self.vc[0] = steer_sim if throttle_out: self.vc[1] = throttle_out @@ -80,24 +80,24 @@ def read_state(self): self.status_q.put(QueueMessage(QueueMessageType.TERMINATION_INFO, md_state.done_info)) self.exit_event.set() - def read_sensors(self, state: SimulatorState): + def read_sensors(self, simulator_state: SimulatorState): while self.vehicle_state_recv.poll(0): md_vehicle: metadrive_vehicle_state = self.vehicle_state_recv.recv() curr_pos = md_vehicle.position - state.velocity = md_vehicle.velocity - state.bearing = md_vehicle.bearing - state.steering_angle = md_vehicle.steering_angle - state.gps.from_xy(curr_pos) - state.valid = True + simulator_state.velocity = md_vehicle.velocity + simulator_state.bearing = md_vehicle.bearing + simulator_state.steering_angle = md_vehicle.steering_angle + simulator_state.gps.from_xy(curr_pos) + simulator_state.valid = True - is_engaged = state.is_engaged + is_engaged = simulator_state.is_engaged if is_engaged and self.first_engage is None: self.first_engage = time.monotonic() self.op_engaged.set() # check moving 5 seconds after engaged, doesn't move right away - after_engaged_check = is_engaged and time.monotonic() - self.first_engage >= 5 and self.test_run + after_engaged_check = is_engaged and self.first_engage is not None and time.monotonic() - self.first_engage >= 5 and self.test_run x_dist = abs(curr_pos[0] - self.vehicle_last_pos[0]) y_dist = abs(curr_pos[1] - self.vehicle_last_pos[1]) diff --git a/tools/tuning/measure_steering_accuracy.py b/tools/tuning/measure_steering_accuracy.py index 7e4e975742f63c..e4aef0ba15c822 100755 --- a/tools/tuning/measure_steering_accuracy.py +++ b/tools/tuning/measure_steering_accuracy.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# type: ignore import os import time diff --git a/uv.lock b/uv.lock index b179517e0b26d1..fdffa1b5ff481d 100644 --- a/uv.lock +++ b/uv.lock @@ -1188,41 +1188,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fd/69/b547032297c7e63ba2af494edba695d781af8a0c6e89e4d06cf848b21d80/multidict-6.6.4-py3-none-any.whl", hash = "sha256:27d8f8e125c07cb954e54d75d04905a9bba8a439c1d84aca94949d4d03d8601c", size = 12313, upload-time = "2025-08-11T12:08:46.891Z" }, ] -[[package]] -name = "mypy" -version = "1.18.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mypy-extensions" }, - { name = "pathspec" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c0/77/8f0d0001ffad290cef2f7f216f96c814866248a0b92a722365ed54648e7e/mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b", size = 3448846, upload-time = "2025-09-19T00:11:10.519Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/87/cafd3ae563f88f94eec33f35ff722d043e09832ea8530ef149ec1efbaf08/mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f", size = 12731198, upload-time = "2025-09-19T00:09:44.857Z" }, - { url = "https://files.pythonhosted.org/packages/0f/e0/1e96c3d4266a06d4b0197ace5356d67d937d8358e2ee3ffac71faa843724/mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341", size = 11817879, upload-time = "2025-09-19T00:09:47.131Z" }, - { url = "https://files.pythonhosted.org/packages/72/ef/0c9ba89eb03453e76bdac5a78b08260a848c7bfc5d6603634774d9cd9525/mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d", size = 12427292, upload-time = "2025-09-19T00:10:22.472Z" }, - { url = "https://files.pythonhosted.org/packages/1a/52/ec4a061dd599eb8179d5411d99775bec2a20542505988f40fc2fee781068/mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86", size = 13163750, upload-time = "2025-09-19T00:09:51.472Z" }, - { url = "https://files.pythonhosted.org/packages/c4/5f/2cf2ceb3b36372d51568f2208c021870fe7834cf3186b653ac6446511839/mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37", size = 13351827, upload-time = "2025-09-19T00:09:58.311Z" }, - { url = "https://files.pythonhosted.org/packages/c8/7d/2697b930179e7277529eaaec1513f8de622818696857f689e4a5432e5e27/mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8", size = 9757983, upload-time = "2025-09-19T00:10:09.071Z" }, - { url = "https://files.pythonhosted.org/packages/07/06/dfdd2bc60c66611dd8335f463818514733bc763e4760dee289dcc33df709/mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34", size = 12908273, upload-time = "2025-09-19T00:10:58.321Z" }, - { url = "https://files.pythonhosted.org/packages/81/14/6a9de6d13a122d5608e1a04130724caf9170333ac5a924e10f670687d3eb/mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764", size = 11920910, upload-time = "2025-09-19T00:10:20.043Z" }, - { url = "https://files.pythonhosted.org/packages/5f/a9/b29de53e42f18e8cc547e38daa9dfa132ffdc64f7250e353f5c8cdd44bee/mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893", size = 12465585, upload-time = "2025-09-19T00:10:33.005Z" }, - { url = "https://files.pythonhosted.org/packages/77/ae/6c3d2c7c61ff21f2bee938c917616c92ebf852f015fb55917fd6e2811db2/mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914", size = 13348562, upload-time = "2025-09-19T00:10:11.51Z" }, - { url = "https://files.pythonhosted.org/packages/4d/31/aec68ab3b4aebdf8f36d191b0685d99faa899ab990753ca0fee60fb99511/mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8", size = 13533296, upload-time = "2025-09-19T00:10:06.568Z" }, - { url = "https://files.pythonhosted.org/packages/9f/83/abcb3ad9478fca3ebeb6a5358bb0b22c95ea42b43b7789c7fb1297ca44f4/mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074", size = 9828828, upload-time = "2025-09-19T00:10:28.203Z" }, - { url = "https://files.pythonhosted.org/packages/87/e3/be76d87158ebafa0309946c4a73831974d4d6ab4f4ef40c3b53a385a66fd/mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e", size = 2352367, upload-time = "2025-09-19T00:10:15.489Z" }, -] - -[[package]] -name = "mypy-extensions" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, -] - [[package]] name = "natsort" version = "8.4.0" @@ -1386,7 +1351,6 @@ testing = [ { name = "codespell" }, { name = "coverage" }, { name = "hypothesis" }, - { name = "mypy" }, { name = "pre-commit-hooks" }, { name = "pytest" }, { name = "pytest-asyncio" }, @@ -1398,6 +1362,7 @@ testing = [ { name = "pytest-timeout" }, { name = "pytest-xdist" }, { name = "ruff" }, + { name = "ty" }, ] tools = [ { name = "dearpygui", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, @@ -1432,7 +1397,6 @@ requires-dist = [ { name = "matplotlib", marker = "extra == 'dev'" }, { name = "metadrive-simulator", marker = "platform_machine != 'aarch64' and extra == 'tools'", url = "https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl" }, { name = "mkdocs", marker = "extra == 'docs'" }, - { name = "mypy", marker = "extra == 'testing'" }, { name = "natsort", marker = "extra == 'docs'" }, { name = "numpy", specifier = ">=2.0" }, { name = "onnx", specifier = ">=1.14.0" }, @@ -1476,6 +1440,7 @@ requires-dist = [ { name = "sympy" }, { name = "tabulate", marker = "extra == 'dev'" }, { name = "tqdm" }, + { name = "ty", marker = "extra == 'testing'" }, { name = "types-requests", marker = "extra == 'dev'" }, { name = "types-tabulate", marker = "extra == 'dev'" }, { name = "websocket-client" }, @@ -4939,6 +4904,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, ] +[[package]] +name = "ty" +version = "0.0.1a34" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/f9/f467d2fbf02a37af5d779eb21c59c7d5c9ce8c48f620d590d361f5220208/ty-0.0.1a34.tar.gz", hash = "sha256:659e409cc3b5c9fb99a453d256402a4e3bd95b1dbcc477b55c039697c807ab79", size = 4735988, upload-time = "2025-12-12T18:29:23.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/b7/d5a5c611baaa20e85971a7c9a527aaf3e8fb47e15de88d1db39c64ee3638/ty-0.0.1a34-py3-none-linux_armv6l.whl", hash = "sha256:00c138e28b12a80577ee3e15fc638eb1e35cf5aa75f5967bf2d1893916ce571c", size = 9708675, upload-time = "2025-12-12T18:29:06.571Z" }, + { url = "https://files.pythonhosted.org/packages/cb/62/0b78976c8da58b90a86d1a1b8816ff4a6e8437f6e52bb6800c4483242e7f/ty-0.0.1a34-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:cbb9c187164675647143ecb56e684d6766f7d5ba7f6874a369fe7c3d380a6c92", size = 9515760, upload-time = "2025-12-12T18:28:56.901Z" }, + { url = "https://files.pythonhosted.org/packages/39/1f/4e3d286b37aab3428a30b8f5db5533b8ce6e23b1bd84f77a137bd782b418/ty-0.0.1a34-py3-none-macosx_11_0_arm64.whl", hash = "sha256:68b2375b366ee799a896594cde393a1b60414efdfd31399c326bfc136bfc41f3", size = 9064633, upload-time = "2025-12-12T18:29:10.211Z" }, + { url = "https://files.pythonhosted.org/packages/5d/31/e17049b868f5cac7590c000f31ff9453e4360125416da4e8195e82b5409a/ty-0.0.1a34-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f6b68d9673e43bdd5bdcaa6b5db50e873431fc44dde5e25e253e8226ec93ac1", size = 9310295, upload-time = "2025-12-12T18:29:21.635Z" }, + { url = "https://files.pythonhosted.org/packages/77/1d/7a89b3032e84a01223d0c33e47f33eef436ca36949b28600554a2a4da1f8/ty-0.0.1a34-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:832b360fd397c076e294c252db52581b9ecb38d8063d6262ac927610540702be", size = 9498451, upload-time = "2025-12-12T18:29:24.955Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5e/e782c4367d14b965b1ee9bddc3f3102982ff1cc2dae699c201ecd655e389/ty-0.0.1a34-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0cb6fc497f1feb67e299fd3507ed30498c7e15b31099b3dcdbeca6b7ac2d3129", size = 9912522, upload-time = "2025-12-12T18:29:00.252Z" }, + { url = "https://files.pythonhosted.org/packages/9c/25/4d72d7174b60adeb9df6e4c5d8552161da2b84ddcebed8ab37d0f7f266ab/ty-0.0.1a34-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:284c8cfd64f255d942ef21953e3d40d087c74dec27e16495bd656decdd208f59", size = 10518743, upload-time = "2025-12-12T18:28:54.944Z" }, + { url = "https://files.pythonhosted.org/packages/05/c5/30a6e377bcab7d5b65d5c78740635b23ecee647bf268c9dc82a91d41c9ba/ty-0.0.1a34-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c34b028305642fd3a9076d4b07d651a819c61a65371ef38cde60f0b54dce6180", size = 10285473, upload-time = "2025-12-12T18:29:08.432Z" }, + { url = "https://files.pythonhosted.org/packages/97/aa/d2cd564ee37a587c8311383a5687584c9aed241a9e67301ee0280301eef3/ty-0.0.1a34-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad997a21648dc64017f11a96b7bb44f088ab0fd589decadc2d686fc97b102f4e", size = 10298873, upload-time = "2025-12-12T18:29:12.38Z" }, + { url = "https://files.pythonhosted.org/packages/2e/80/c427dabd51b5d8b50fc375e18674c098877a9d6545af810ccff4e40ff74a/ty-0.0.1a34-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1afe9798f94c0fbb9e42ff003dfcb4df982f97763d93e5b1d53f9da865a53af", size = 9851399, upload-time = "2025-12-12T18:29:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/cc/d8/7240c0e13bc3405b190b4437fbc67c86aa70e349b282e5fa79282181532b/ty-0.0.1a34-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:bd335010aa211fbf8149d3507d6331bdb947d5328ca31388cecdbd2eb49275c3", size = 9261475, upload-time = "2025-12-12T18:29:04.638Z" }, + { url = "https://files.pythonhosted.org/packages/6b/a1/6538f8fe7a5b1a71b20461d905969b7f62574cf9c8c6af580b765a647289/ty-0.0.1a34-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:29ebcc56aabaf6aa85c3baf788e211455ffc9935b807ddc9693954b6990e9a3c", size = 9554878, upload-time = "2025-12-12T18:29:16.349Z" }, + { url = "https://files.pythonhosted.org/packages/3d/f2/b8ab163b928de329d88a5f04a5c399a40c1c099b827c70e569e539f9a755/ty-0.0.1a34-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0cbb5a68fddec83c39db6b5f0a5c5da5a3f7d7620e4bcb4ad5bf3a0c7f89ab45", size = 9651340, upload-time = "2025-12-12T18:29:19.92Z" }, + { url = "https://files.pythonhosted.org/packages/dc/1b/1e4e24b684ee5f22dda18d86846430b123fb2e985f0c0eb986e6eccec1b9/ty-0.0.1a34-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f9b3fd934982a9497237bf39fa472f6d201260ac95b3dc75ba9444d05ec01654", size = 9944488, upload-time = "2025-12-12T18:28:58.544Z" }, + { url = "https://files.pythonhosted.org/packages/80/b0/6435f1795f76c57598933624af58bf67385c96b8fa3252f5f9087173e21a/ty-0.0.1a34-py3-none-win32.whl", hash = "sha256:bdabc3f1a048bc2891d4184b818a7ee855c681dd011d00ee672a05bfe6451156", size = 9151401, upload-time = "2025-12-12T18:28:53.028Z" }, + { url = "https://files.pythonhosted.org/packages/73/2e/adce0d7c07f6de30c7f3c125744ec818c7f04b14622a739fe17d4d0bdb93/ty-0.0.1a34-py3-none-win_amd64.whl", hash = "sha256:a4caa2e58685d6801719becbd0504fe61e3ab94f2509e84759f755a0ca480ada", size = 10031079, upload-time = "2025-12-12T18:29:14.556Z" }, + { url = "https://files.pythonhosted.org/packages/23/0d/1f123c69ce121dcabf5449a456a9a37c3bbad396e9e7484514f1fe568f96/ty-0.0.1a34-py3-none-win_arm64.whl", hash = "sha256:dd02c22b538657b042d154fe2d5e250dfb20c862b32e6036a6ffce2fd1ebca9d", size = 9534879, upload-time = "2025-12-12T18:29:18.187Z" }, +] + [[package]] name = "types-requests" version = "2.32.4.20250913"