From ea01a53711a01d4d88c1e4e8ae9cb7e93b7a070e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 28 Dec 2025 10:42:49 -0800 Subject: [PATCH] switch from mypy to ty (#36961) --- common/git.py | 12 +-- common/prefix.py | 2 +- pyproject.toml | 78 ++++++++++--------- scripts/lint/lint.sh | 12 +-- selfdrive/debug/cycle_alerts.py | 6 +- selfdrive/debug/fingerprint_from_route.py | 11 +-- selfdrive/locationd/calibrationd.py | 2 +- selfdrive/locationd/helpers.py | 2 +- selfdrive/selfdrived/alertmanager.py | 2 +- selfdrive/test/fuzzy_generation.py | 2 +- selfdrive/test/process_replay/migration.py | 3 +- .../test/process_replay/process_replay.py | 6 +- selfdrive/test/process_replay/regen.py | 2 +- selfdrive/test/update_ci_routes.py | 2 +- selfdrive/ui/layouts/home.py | 2 +- selfdrive/ui/mici/widgets/button.py | 16 ++-- selfdrive/ui/mici/widgets/dialog.py | 4 +- selfdrive/ui/translations/auto_translate.py | 2 +- system/athena/athenad.py | 4 +- system/athena/tests/test_athenad.py | 2 +- system/loggerd/tests/loggerd_tests_common.py | 4 +- system/loggerd/tests/test_uploader.py | 2 +- system/loggerd/uploader.py | 2 +- system/manager/process.py | 2 +- system/ui/mici_updater.py | 17 ++-- system/ui/tici_updater.py | 17 ++-- system/ui/widgets/__init__.py | 6 +- system/ui/widgets/label.py | 4 +- system/ui/widgets/list_view.py | 11 +-- system/updated/casync/casync.py | 4 +- system/updated/updated.py | 2 +- system/webrtc/device/audio.py | 4 +- tools/jotpluggler/data.py | 2 +- tools/jotpluggler/views.py | 2 +- tools/lib/logreader.py | 5 +- tools/lib/vidindex.py | 2 +- uv.lock | 64 +++++++-------- 37 files changed, 160 insertions(+), 162 deletions(-) diff --git a/common/git.py b/common/git.py index 2296fa7088..6b662e5719 100644 --- a/common/git.py +++ b/common/git.py @@ -4,27 +4,27 @@ from openpilot.common.utils import run_cmd, run_cmd_default @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/prefix.py b/common/prefix.py index 86f69671d8..d0a5f92628 100644 --- a/common/prefix.py +++ b/common/prefix.py @@ -10,7 +10,7 @@ from openpilot.system.hardware.hw import Paths 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]) shm_path = "/tmp" if platform.system() == "Darwin" else "/dev/shm" self.msgq_path = os.path.join(shm_path, "msgq_" + self.prefix) diff --git a/pyproject.toml b/pyproject.toml index fc80488c69..bb4ac0e8d8 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,42 +180,6 @@ 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" -exclude = [ - "cereal/", - "msgq/", - "msgq_repo/", - "opendbc/", - "opendbc_repo/", - "panda/", - "rednose/", - "rednose_repo/", - "tinygrad/", - "tinygrad_repo/", - "teleoprtc/", - "teleoprtc_repo/", - "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 - -# allow implicit optionals for default args -implicit_optional = true - -local_partial_types=true -explicit_package_bases=true -disable_error_code = "annotation-unchecked" - # https://beta.ruff.rs/docs/configuration/#using-pyprojecttoml [tool.ruff] indent-width = 2 @@ -274,3 +238,43 @@ lint.flake8-implicit-str-concat.allow-multiline = false [tool.ruff.format] quote-style = "preserve" + +[tool.ty.src] +exclude = [ + "cereal/", + "msgq/", + "msgq_repo/", + "opendbc/", + "opendbc_repo/", + "panda/", + "rednose/", + "rednose_repo/", + "tinygrad/", + "tinygrad_repo/", + "teleoprtc/", + "teleoprtc_repo/", + "third_party/", +] + +[tool.ty.rules] +# Ignore unresolved imports for Cython-compiled modules (.pyx) +unresolved-import = "ignore" +# Ignore unresolved attributes - many from capnp and Cython modules +unresolved-attribute = "ignore" +# Ignore invalid method overrides - signature variance issues +invalid-method-override = "ignore" +# Ignore possibly-missing-attribute - too many false positives +possibly-missing-attribute = "ignore" +# Ignore invalid assignment - often intentional monkey-patching +invalid-assignment = "ignore" +# Ignore no-matching-overload - numpy/ctypes overload matching issues +no-matching-overload = "ignore" +# Ignore invalid-argument-type - many false positives from raylib, ctypes, numpy +invalid-argument-type = "ignore" +# Ignore call-non-callable - false positives from dynamic types +call-non-callable = "ignore" +# Ignore unsupported-operator - false positives from dynamic types +unsupported-operator = "ignore" +# Ignore non-subscriptable - false positives from dynamic types +non-subscriptable = "ignore" +# not-iterable errors are now fixed diff --git a/scripts/lint/lint.sh b/scripts/lint/lint.sh index 578c63cd18..5581171e8f 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/debug/cycle_alerts.py b/selfdrive/debug/cycle_alerts.py index 11e75a7a8e..00fa33ac63 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 179ff4c838..5fd46f5b76 100755 --- a/selfdrive/debug/fingerprint_from_route.py +++ b/selfdrive/debug/fingerprint_from_route.py @@ -28,11 +28,12 @@ def get_fingerprint(lr): # TODO: also print the fw fingerprint merged with the existing ones # show FW fingerprint - print("\nFW fingerprint:\n") - for f in fw: - print(f" (Ecu.{f.ecu}, {hex(f.address)}, {None if f.subAddress == 0 else f.subAddress}): [") - print(f" {f.fwVersion},") - print(" ],") + if fw: + print("\nFW fingerprint:\n") + for f in fw: + print(f" (Ecu.{f.ecu}, {hex(f.address)}, {None if f.subAddress == 0 else f.subAddress}): [") + print(f" {f.fwVersion},") + print(" ],") print() print(f"VIN: {vin}") diff --git a/selfdrive/locationd/calibrationd.py b/selfdrive/locationd/calibrationd.py index 03c044982e..3843cf37f3 100755 --- a/selfdrive/locationd/calibrationd.py +++ b/selfdrive/locationd/calibrationd.py @@ -92,7 +92,7 @@ class Calibrator: 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 2a3ac8b861..73c4d8bf35 100644 --- a/selfdrive/locationd/helpers.py +++ b/selfdrive/locationd/helpers.py @@ -94,7 +94,7 @@ class PointBuckets: 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/selfdrived/alertmanager.py b/selfdrive/selfdrived/alertmanager.py index 251d32ba9a..385c276a94 100644 --- a/selfdrive/selfdrived/alertmanager.py +++ b/selfdrive/selfdrived/alertmanager.py @@ -13,7 +13,7 @@ with open(os.path.join(BASEDIR, "selfdrive/selfdrived/alerts_offroad.json")) as 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 94eb0dfaa6..131dab47b2 100644 --- a/selfdrive/test/fuzzy_generation.py +++ b/selfdrive/test/fuzzy_generation.py @@ -44,7 +44,7 @@ class FuzzyGenerator: 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 33b363cfd9..232722d1b1 100644 --- a/selfdrive/test/process_replay/migration.py +++ b/selfdrive/test/process_replay/migration.py @@ -1,5 +1,6 @@ from collections import defaultdict from collections.abc import Callable +from typing import cast import capnp import functools import traceback @@ -67,7 +68,7 @@ def migrate(lr: LogIterable, migration_funcs: list[MigrationFunc]): if migration.product in grouped: # skip if product already exists continue - sorted_indices = sorted(ii for i in migration.inputs for ii in grouped[i]) + sorted_indices = sorted(ii for i in cast(list[str], migration.inputs) for ii in grouped.get(i, [])) msg_gen = [(i, lr[i]) for i in sorted_indices] r_ops, a_ops, d_ops = migration(msg_gen) replace_ops.extend(r_ops) diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index 1144b7955e..8af72e5f4e 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -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 ec35a5c3ac..c501a4b250 100755 --- a/selfdrive/test/process_replay/regen.py +++ b/selfdrive/test/process_replay/regen.py @@ -16,7 +16,7 @@ from openpilot.tools.lib.openpilotci import get_url 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 2bf06b4860..54e1c88718 100755 --- a/selfdrive/test/update_ci_routes.py +++ b/selfdrive/test/update_ci_routes.py @@ -19,7 +19,7 @@ SOURCES: list[AzureContainer] = [ 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 cd6ae600ef..a8404a20c3 100644 --- a/selfdrive/ui/layouts/home.py +++ b/selfdrive/ui/layouts/home.py @@ -39,7 +39,7 @@ class HomeLayout(Widget): 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/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index be08e0fee3..82310577b0 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -71,7 +71,7 @@ class BigCircleButton(Widget): 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 @@ -251,7 +251,7 @@ class BigButton(Widget): 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 @@ -288,8 +288,8 @@ class BigToggle(BigButton): 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 @@ -327,8 +327,8 @@ class BigMultiToggle(BigToggle): 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 @@ -345,7 +345,7 @@ class BigMultiParamToggle(BigMultiToggle): 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 self.params = Params() @@ -361,7 +361,7 @@ class BigParamControl(BigToggle): # 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 self.params = Params() diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 34760925a1..abd558aa8d 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -137,7 +137,7 @@ class BigInputDialog(BigDialogBase): 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) @@ -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: diff --git a/selfdrive/ui/translations/auto_translate.py b/selfdrive/ui/translations/auto_translate.py index 6251e03397..9354790f94 100755 --- a/selfdrive/ui/translations/auto_translate.py +++ b/selfdrive/ui/translations/auto_translate.py @@ -18,7 +18,7 @@ OPENAI_PROMPT = "You are a professional translator from English to {language} (I "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/system/athena/athenad.py b/system/athena/athenad.py index 3b71a9c31f..b52ef21ba6 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 99ac3b1c6b..5b3e0b41f4 100644 --- a/system/athena/tests/test_athenad.py +++ b/system/athena/tests/test_athenad.py @@ -97,7 +97,7 @@ class TestAthenadMethods: 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 87c3da65c2..8bf609ae8d 100644 --- a/system/loggerd/tests/loggerd_tests_common.py +++ b/system/loggerd/tests/loggerd_tests_common.py @@ -10,7 +10,7 @@ from openpilot.system.hardware.hw import Paths 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: @@ -80,7 +80,7 @@ class UploaderTestCase: 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_uploader.py b/system/loggerd/tests/test_uploader.py index 961a8aa36f..562bc068eb 100644 --- a/system/loggerd/tests/test_uploader.py +++ b/system/loggerd/tests/test_uploader.py @@ -50,7 +50,7 @@ class TestUploader(UploaderTestCase): 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 5b6234e1d5..8ac38b6df6 100755 --- a/system/loggerd/uploader.py +++ b/system/loggerd/uploader.py @@ -226,7 +226,7 @@ class Uploader: 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/process.py b/system/manager/process.py index 1e24198267..36e1ba77b2 100644 --- a/system/manager/process.py +++ b/system/manager/process.py @@ -81,7 +81,7 @@ class ManagerProcess(ABC): 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/ui/mici_updater.py b/system/ui/mici_updater.py index 2ae2f7cc19..7ebb4262ff 100755 --- a/system/ui/mici_updater.py +++ b/system/ui/mici_updater.py @@ -102,14 +102,15 @@ class Updater(Widget): self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1, universal_newlines=True) - for line in self.process.stdout: - parts = line.strip().split(":") - if len(parts) == 2: - self.progress_text = parts[0].lower() - try: - self.progress_value = int(float(parts[1])) - except ValueError: - pass + if self.process.stdout is not None: + for line in self.process.stdout: + parts = line.strip().split(":") + if len(parts) == 2: + self.progress_text = parts[0].lower() + try: + self.progress_value = int(float(parts[1])) + except ValueError: + pass exit_code = self.process.wait() if exit_code == 0: diff --git a/system/ui/tici_updater.py b/system/ui/tici_updater.py index 2e1a8687e1..ebf4b3bec3 100755 --- a/system/ui/tici_updater.py +++ b/system/ui/tici_updater.py @@ -71,14 +71,15 @@ class Updater(Widget): self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1, universal_newlines=True) - for line in self.process.stdout: - parts = line.strip().split(":") - if len(parts) == 2: - self.progress_text = parts[0] - try: - self.progress_value = int(float(parts[1])) - except ValueError: - pass + if self.process.stdout is not None: + for line in self.process.stdout: + parts = line.strip().split(":") + if len(parts) == 2: + self.progress_text = parts[0] + try: + self.progress_value = int(float(parts[1])) + except ValueError: + pass exit_code = self.process.wait() if exit_code == 0: diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index 8a46a3e43d..4f3c977d2e 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import abc import pyray as rl from enum import IntEnum @@ -91,7 +93,7 @@ class Widget(abc.ABC): 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) @@ -359,7 +361,7 @@ class NavWidget(Widget, abc.ABC): 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/label.py b/system/ui/widgets/label.py index 97b293083d..cb0cf66b14 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): diff --git a/system/ui/widgets/list_view.py b/system/ui/widgets/list_view.py index cfb8ab58a3..32bf01cfc8 100644 --- a/system/ui/widgets/list_view.py +++ b/system/ui/widgets/list_view.py @@ -174,8 +174,8 @@ class TextAction(ItemAction): 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 @@ class DualButtonAction(ItemAction): 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 @@ -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, +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/updated/casync/casync.py b/system/updated/casync/casync.py index 79ac26f1c6..2bd46a1ffb 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 a4a1f8f34f..ffd10e038d 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 4b22033e03..b1859518a1 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 @@ class AudioInputStreamTrack(aiortc.mediastreams.AudioStreamTrack): 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 cf27857d1f..756b87bd20 100644 --- a/tools/jotpluggler/data.py +++ b/tools/jotpluggler/data.py @@ -9,7 +9,7 @@ from openpilot.selfdrive.test.process_replay.migration import migrate_all 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/views.py b/tools/jotpluggler/views.py index 1c4d9a8f3c..cd723d161e 100644 --- a/tools/jotpluggler/views.py +++ b/tools/jotpluggler/views.py @@ -9,7 +9,7 @@ from abc import ABC, abstractmethod 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" diff --git a/tools/lib/logreader.py b/tools/lib/logreader.py index f9a90490b9..9696c8524d 100755 --- a/tools/lib/logreader.py +++ b/tools/lib/logreader.py @@ -13,7 +13,6 @@ import warnings 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 @@ class LogReader: 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 f2e4e9ca45..4aef6fb4d5 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 = int(2**(prefix_len-1) - 1 + suffix_val) size = prefix_len + suffix_len return val, size i += 1 diff --git a/uv.lock b/uv.lock index 53cb108c61..d8e3d82696 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" }, @@ -4945,6 +4910,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.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/43/8be3ec2e2ce6119cff9ee3a207fae0cb4f2b4f8ed6534175130a32be24a7/ty-0.0.7.tar.gz", hash = "sha256:90e53b20b86c418ee41a8385f17da44cc7f916f96f9eee87593423ce8292ca72", size = 4826677, upload-time = "2025-12-24T21:28:49.136Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/56/fafa123acf955089306372add312f16e97aba61f7c4daf74e2bb9c350d23/ty-0.0.7-py3-none-linux_armv6l.whl", hash = "sha256:b30105bd9a0b064497111c50c206d5b6a032f29bcf39f09a12085c3009d72784", size = 9862360, upload-time = "2025-12-24T21:28:36.762Z" }, + { url = "https://files.pythonhosted.org/packages/71/f4/9c30ff498d9a60e24f16d26c0cf93cd03a119913ffa720a77149f02df06e/ty-0.0.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b4df20889115f3d5611a9d9cdedc222e3fd82b5fe87bb0a9f7246e53a23becc7", size = 9712866, upload-time = "2025-12-24T21:28:25.926Z" }, + { url = "https://files.pythonhosted.org/packages/43/84/e06a4a6e4011890027ffee41efbf261b1335103d09009d625ace7f1a60eb/ty-0.0.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f699589d8511e1e17c5a7edfc5f4a4e80f2a6d4a3932a0e9e3422fd32d731472", size = 9221692, upload-time = "2025-12-24T21:28:29.649Z" }, + { url = "https://files.pythonhosted.org/packages/7a/e9/ebb4192d3627730125d40ee403a17dc91bab59d69c3eff286453b3218d01/ty-0.0.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3eaec2d8aa153ee4bcc43b17a384d0f9e66177c8c8127be3358b6b8348b9e3b", size = 9710340, upload-time = "2025-12-24T21:28:55.148Z" }, + { url = "https://files.pythonhosted.org/packages/8f/4a/ec144458a9cfb324d5cb471483094e62e74d73179343dff262a5cca1a1e1/ty-0.0.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:177d160295e6a56bdf0b61f6120bc4502fff301d4d10855ba711c109aa7f37fb", size = 9670317, upload-time = "2025-12-24T21:28:43.096Z" }, + { url = "https://files.pythonhosted.org/packages/b6/94/fe7106fd5e2ac06b81fba7b785a6216774618edc3fda9e17f58efe3cede6/ty-0.0.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30518b95ab5cc83615794cca765a5fb86df39a0d9c3dadc0ab2d787ab7830008", size = 10096517, upload-time = "2025-12-24T21:28:23.667Z" }, + { url = "https://files.pythonhosted.org/packages/45/d9/db96ccfd663c96bdd4bb63db72899198c01445012f939477a5318a563f14/ty-0.0.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7867b3f75c2d9602cc6fb3b6d462580b707c2d112d4b27037142b0d01f8bfd03", size = 10996406, upload-time = "2025-12-24T21:28:39.134Z" }, + { url = "https://files.pythonhosted.org/packages/94/da/103915c08c3e6a14f95959614646fcdc9a240cd9a039fadbdcd086c819ee/ty-0.0.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:878d45858e209b7904753fbc5155f4cb75dadc20a26bbb77614bfef31580f9ae", size = 10712829, upload-time = "2025-12-24T21:28:27.745Z" }, + { url = "https://files.pythonhosted.org/packages/47/c0/d9be417bc8e459e13e9698978579eec9868f91f4c5d6ef663249967fec8b/ty-0.0.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:651820b193901825afce40ae68f6a51cd64dbfa4b81a45db90061401261f25e4", size = 10486541, upload-time = "2025-12-24T21:28:45.17Z" }, + { url = "https://files.pythonhosted.org/packages/ad/09/d1858c66620d8ae566e021ad0d7168914b1568841f8fe9e439116ce6b440/ty-0.0.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f56a5a0c1c045863b1b70c358a392b3f73b8528c5c571d409f19dd465525e116", size = 10255312, upload-time = "2025-12-24T21:28:53.17Z" }, + { url = "https://files.pythonhosted.org/packages/b6/0a/78f75089db491fd5fcc13d2845a0b2771b7f7d377450c64c6616e9c227bc/ty-0.0.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:748218fbc1f7b7f1b9d14e77d4f3d7fec72af794417e26b0185bdb94153afe1c", size = 9696201, upload-time = "2025-12-24T21:28:57.345Z" }, + { url = "https://files.pythonhosted.org/packages/01/9e/b26e94832fd563fef6f77a4487affc77a027b0e53106422c66aafb37fa01/ty-0.0.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1ff80f3985a52a7358b9069b4a8d223e92cf312544a934a062d6d3a4fb6876b3", size = 9688907, upload-time = "2025-12-24T21:28:59.485Z" }, + { url = "https://files.pythonhosted.org/packages/5a/8f/cc48601fb92c964cf6c34277e0d947076146b7de47aa11b5dbae45e01ce7/ty-0.0.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a808910ce672ba4446699f4c021283208f58f988bcfc3bdbdfc6e005819d9ee0", size = 9829982, upload-time = "2025-12-24T21:28:34.429Z" }, + { url = "https://files.pythonhosted.org/packages/b5/af/7fa9c2bfa25865968bded637f7e71f1a712f4fbede88f487b6a9101ab936/ty-0.0.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:2718fea5f314eda01703fb406ec89b1fc8710b3fc6a09bbd6f7a4f3502ddc889", size = 10361037, upload-time = "2025-12-24T21:28:47.027Z" }, + { url = "https://files.pythonhosted.org/packages/1c/5b/1a6ff1495975cd1c02aa8d03bc5c9d8006eaeb8bf354446f88d70f0518fd/ty-0.0.7-py3-none-win32.whl", hash = "sha256:ae89bb8dc50deb66f34eab3113aa61ac5d7f85ecf16279e5918548085a89021c", size = 9295092, upload-time = "2025-12-24T21:28:51.041Z" }, + { url = "https://files.pythonhosted.org/packages/ff/f6/47e9364635d048002354f84d2d0d6dfc9eb166dc67850739f88e1fec4fc5/ty-0.0.7-py3-none-win_amd64.whl", hash = "sha256:25bd20e3d4d0f07b422f9b42711ba24d28116031273bd23dbda66cec14df1c06", size = 10162816, upload-time = "2025-12-24T21:28:41.006Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f4/c4fc28410c4493982b7481fb23f62bacb02fd2912ebec3b9bc7de18bebb8/ty-0.0.7-py3-none-win_arm64.whl", hash = "sha256:c87d27484dba9fca0053b6a9eee47eecc760aab2bbb8e6eab3d7f81531d1ad0c", size = 9653112, upload-time = "2025-12-24T21:28:31.562Z" }, +] + [[package]] name = "types-requests" version = "2.32.4.20250913"