From 8860f1a0a78505b017ea78f75fe98f62b64c8437 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 7 Jul 2026 13:56:54 +0530 Subject: [PATCH] ty does not enforce existence of type signatures on functions, so use ruff to do it --- .github/workflows/macos_crash_report.py | 6 ++-- bypy/init_env.py | 4 +-- bypy/linux/__main__.py | 22 ++++++------ bypy/macos/__main__.py | 48 ++++++++++++------------- glad/generate.py | 8 ++--- kitty/fonts/common.py | 2 +- kitty/types.py | 46 ++++++++++++------------ pyproject.toml | 11 ++++++ 8 files changed, 78 insertions(+), 69 deletions(-) diff --git a/.github/workflows/macos_crash_report.py b/.github/workflows/macos_crash_report.py index 31df82ca1..8c4c975e1 100755 --- a/.github/workflows/macos_crash_report.py +++ b/.github/workflows/macos_crash_report.py @@ -285,7 +285,7 @@ class CrashReportBase: self._data = data self._parse() - def _parse(self): + def _parse(self) -> None: self._is_json = False try: modified_data = self._data @@ -307,7 +307,7 @@ class CrashReportBase: return self._metadata['bug_type'] @cached_property - def incident_id(self): + def incident_id(self) -> str: return self._metadata.get('incident_id') @cached_property @@ -428,7 +428,7 @@ class UserModeCrashReport(CrashReportBase): return result @cached_property - def exception_type(self): + def exception_type(self) -> str: if self._is_json: return self._data['exception'].get('type') else: diff --git a/bypy/init_env.py b/bypy/init_env.py index 790eda123..c4386faf8 100644 --- a/bypy/init_env.py +++ b/bypy/init_env.py @@ -60,7 +60,7 @@ def build_frozen_launcher(extra_include_dirs): return build_frozen_launcher.writeable_src_dir -def run_tests(kitty_exe): +def run_tests(kitty_exe) -> None: with tempfile.TemporaryDirectory() as tdir: uenv = { 'KITTY_CONFIG_DIRECTORY': os.path.join(tdir, 'conf'), @@ -78,7 +78,7 @@ def run_tests(kitty_exe): raise SystemExit('Checking of kitty build failed') -def build_frozen_tools(kitty_exe): +def build_frozen_tools(kitty_exe) -> None: cmd = SETUP_CMD + ['--prefix', os.path.dirname(kitty_exe)] + ['build-frozen-tools'] if run(*cmd, cwd=build_frozen_launcher.writeable_src_dir) != 0: print('Building of frozen kitten failed', file=sys.stderr) diff --git a/bypy/linux/__main__.py b/bypy/linux/__main__.py index 777d7dd5b..8c8f5d819 100644 --- a/bypy/linux/__main__.py +++ b/bypy/linux/__main__.py @@ -40,7 +40,7 @@ def binary_includes(): class Env: - def __init__(self, package_dir): + def __init__(self, package_dir) -> None: self.base = package_dir self.lib_dir = j(self.base, 'lib') self.py_dir = j(self.lib_dir, f'python{py_ver}') @@ -65,7 +65,7 @@ def ignore_in_lib(base, items, ignored_dirs=None): return ans -def import_site_packages(srcdir, dest): +def import_site_packages(srcdir, dest) -> None: if not os.path.exists(dest): os.mkdir(dest) for x in os.listdir(srcdir): @@ -82,7 +82,7 @@ def import_site_packages(srcdir, dest): shutil.copytree(f, j(dest, x), ignore=ignore_in_lib) -def copy_libs(env): +def copy_libs(env) -> None: print('Copying libs...') for x in binary_includes(): @@ -92,7 +92,7 @@ def copy_libs(env): subprocess.check_call(['chrpath', '-d', dest]) -def add_ca_certs(env): +def add_ca_certs(env) -> None: print('Downloading CA certs...') from urllib.request import urlopen cdata = urlopen(kitty_constants['cacerts_url']).read() @@ -101,7 +101,7 @@ def add_ca_certs(env): f.write(cdata) -def copy_python(env): +def copy_python(env) -> None: print('Copying python...') srcdir = j(PREFIX, f'lib/python{py_ver}') @@ -138,7 +138,7 @@ def copy_python(env): shutil.rmtree(env.py_dir) -def build_launcher(env): +def build_launcher(env) -> None: iv['build_frozen_launcher']([path_to_freeze_dir(), env.obj_dir]) @@ -147,7 +147,7 @@ def is_elf(path): return f.read(4) == b'\x7fELF' -def fix_permissions(files): +def fix_permissions(files) -> None: for path in files: os.chmod(path, 0o755) @@ -166,7 +166,7 @@ def find_binaries(env): return files -def strip_files(files, argv_max=(256 * 1024)): +def strip_files(files, argv_max=(256 * 1024)) -> None: """ Strip a list of files """ while files: cmd = list(STRIPCMD) @@ -183,7 +183,7 @@ def strip_files(files, argv_max=(256 * 1024)): [os.chmod(x, old_mode) for x, old_mode in unwritable_files] -def strip_binaries(files): +def strip_binaries(files) -> None: print(f'Stripping {len(files)} files...') before = sum(os.path.getsize(x) for x in files) strip_files(files) @@ -191,7 +191,7 @@ def strip_binaries(files): print('Stripped {:.1f} MB'.format((before - after) / (1024 * 1024.))) -def create_tarfile(env, compression_level='9'): +def create_tarfile(env, compression_level='9') -> None: print('Creating archive...') base = OUTPUT_DIR arch = 'arm64' if 'arm64' in os.environ['BYPY_ARCH'] else ('i686' if 'i386' in os.environ['BYPY_ARCH'] else 'x86_64') @@ -222,7 +222,7 @@ def create_tarfile(env, compression_level='9'): os.path.basename(ans), os.stat(ans).st_size / (1024.**2))) -def main(): +def main() -> None: args = globals()['args'] ext_dir = globals()['ext_dir'] env = Env(os.path.join(ext_dir, kitty_constants['appname'])) diff --git a/bypy/macos/__main__.py b/bypy/macos/__main__.py index 2ac9f46e1..bd08aa97f 100644 --- a/bypy/macos/__main__.py +++ b/bypy/macos/__main__.py @@ -56,7 +56,7 @@ def flipwritable(fn, mode=None): STRIPCMD = ('/usr/bin/strip', '-x', '-S', '-') -def strip_files(files, argv_max=(256 * 1024)): +def strip_files(files, argv_max=(256 * 1024)) -> None: """ Strip a list of files """ @@ -120,7 +120,7 @@ def do_sign(app_dir: str) -> None: verify_signature(app_dir) -def sign_app(app_dir, notarize): +def sign_app(app_dir, notarize) -> None: # Copied from iTerm2: https://github.com/gnachman/iTerm2/blob/master/iTerm2.entitlements create_entitlements_file({ 'com.apple.security.automation.apple-events': True, @@ -142,7 +142,7 @@ class Freeze(object): FID = '@executable_path/../Frameworks' - def __init__(self, build_dir, dont_strip=False, sign_installers=False, notarize=False, skip_tests=False): + def __init__(self, build_dir, dont_strip=False, sign_installers=False, notarize=False, skip_tests=False) -> None: self.build_dir = build_dir self.skip_tests = skip_tests self.sign_installers = sign_installers @@ -160,7 +160,7 @@ class Freeze(object): self.run() - def run_shell(self): + def run_shell(self) -> None: with current_dir(self.contents_dir): run_shell() @@ -185,7 +185,7 @@ class Freeze(object): return ret @flush - def complete_sub_bundles(self): + def complete_sub_bundles(self) -> None: count = 0 for qapp in glob.glob(join(self.contents_dir, '*.app')): for exe in glob.glob(join(self.contents_dir, 'MacOS', '*')): @@ -195,7 +195,7 @@ class Freeze(object): raise SystemExit(f'Could not complete sub-bundles in {self.contents_dir}') @flush - def add_ca_certs(self): + def add_ca_certs(self) -> None: print('\nDownloading CA certs...') from urllib.request import urlopen cdata = None @@ -213,16 +213,16 @@ class Freeze(object): f.write(cdata) @flush - def strip_files(self): + def strip_files(self) -> None: print('\nStripping files...') strip_files(self.to_strip) @flush - def run_tests(self): + def run_tests(self) -> None: iv['run_tests'](join(self.contents_dir, 'MacOS', 'kitty')) @flush - def set_id(self, path_to_lib, new_id): + def set_id(self, path_to_lib, new_id) -> None: old_mode = flipwritable(path_to_lib) subprocess.check_call( ['install_name_tool', '-id', new_id, path_to_lib]) @@ -252,12 +252,12 @@ class Freeze(object): break @flush - def change_dep(self, old_dep, new_dep, is_id, path_to_lib): + def change_dep(self, old_dep, new_dep, is_id, path_to_lib) -> None: cmd = ['-id', new_dep] if is_id else ['-change', old_dep, new_dep] subprocess.check_call(['install_name_tool'] + cmd + [path_to_lib]) @flush - def fix_dependencies_in_lib(self, path_to_lib): + def fix_dependencies_in_lib(self, path_to_lib) -> None: self.to_strip.append(path_to_lib) old_mode = flipwritable(path_to_lib) for dep, bname, is_id in self.get_local_dependencies(path_to_lib): @@ -272,7 +272,7 @@ class Freeze(object): flipwritable(path_to_lib, old_mode) @flush - def add_python_framework(self): + def add_python_framework(self) -> None: print('\nAdding Python framework') src = join(f'{PREFIX}/python', 'Python.framework') x = join(self.frameworks_dir, 'Python.framework') @@ -292,7 +292,7 @@ class Freeze(object): os.symlink(f'Versions/Current/{y}', y) @flush - def install_dylib(self, path, set_id=True): + def install_dylib(self, path, set_id=True) -> None: shutil.copy2(path, self.frameworks_dir) if set_id: self.set_id( @@ -301,7 +301,7 @@ class Freeze(object): self.fix_dependencies_in_lib(join(self.frameworks_dir, basename(path))) @flush - def add_misc_libraries(self): + def add_misc_libraries(self) -> None: for x in ( 'sqlite3.0', 'z.1', @@ -321,7 +321,7 @@ class Freeze(object): self.fix_dependencies_in_lib(dest) @flush - def add_package_dir(self, x, dest=None): + def add_package_dir(self, x, dest=None) -> None: def ignore(root, files): ans = [] for y in files: @@ -341,7 +341,7 @@ class Freeze(object): self.fix_dependencies_in_lib(f) @flush - def add_stdlib(self): + def add_stdlib(self) -> None: print('\nAdding python stdlib') src = f'{PREFIX}/python/Python.framework/Versions/Current/lib/python{self.py_ver}' dest = self.python_stdlib @@ -362,7 +362,7 @@ class Freeze(object): self.fix_dependencies_in_lib(dest2) @flush - def freeze_python(self): + def freeze_python(self) -> None: print('\nFreezing python') kitty_dir = join(self.resources_dir, 'kitty') bases = ('kitty', 'kittens', 'kitty_tests') @@ -395,11 +395,11 @@ class Freeze(object): self.fix_dependencies_in_lib(f) @flush - def build_frozen_tools(self): + def build_frozen_tools(self) -> None: iv['build_frozen_tools'](join(self.contents_dir, 'MacOS', 'kitty')) @flush - def add_site_packages(self): + def add_site_packages(self) -> None: print('\nAdding site-packages') os.makedirs(self.site_packages) sys_path = json.loads(subprocess.check_output([ @@ -426,14 +426,14 @@ class Freeze(object): self.remove_bytecode(self.site_packages) @flush - def add_modules_from_dir(self, src): + def add_modules_from_dir(self, src) -> None: for x in glob.glob(join(src, '*.py')) + glob.glob(join(src, '*.so')): shutil.copy2(x, self.site_packages) if x.endswith('.so'): self.fix_dependencies_in_lib(x) @flush - def add_packages_from_dir(self, src): + def add_packages_from_dir(self, src) -> None: for x in os.listdir(src): x = join(src, x) if os.path.isdir(x) and os.path.exists(join(x, '__init__.py')): @@ -447,7 +447,7 @@ class Freeze(object): 'bdist_mpkg', 'altgraph') @flush - def remove_bytecode(self, dest): + def remove_bytecode(self, dest) -> None: for x in os.walk(dest): root = x[0] for f in x[-1]: @@ -455,7 +455,7 @@ class Freeze(object): os.remove(join(root, f)) @flush - def compile_py_modules(self): + def compile_py_modules(self) -> None: self.remove_bytecode(join(self.resources_dir, 'Python')) py_compile(join(self.resources_dir, 'Python')) @@ -503,7 +503,7 @@ class Freeze(object): return dmg -def main(): +def main() -> None: args = globals()['args'] ext_dir = globals()['ext_dir'] Freeze( diff --git a/glad/generate.py b/glad/generate.py index 0f752be89..80c3744ed 100755 --- a/glad/generate.py +++ b/glad/generate.py @@ -16,12 +16,12 @@ cmdline = ( ) -def clean(x): +def clean(x: str) -> None: if os.path.exists(x): shutil.rmtree(x) -def regenerate(): +def regenerate() -> None: clean('out') subprocess.check_call( @@ -29,11 +29,11 @@ def regenerate(): ) -def strip_trailing_whitespace(c): +def strip_trailing_whitespace(c: str) -> str: return re.sub(r'\s+$', '', c, flags=re.MULTILINE) + '\n' -def export(): +def export() -> None: with open('out/include/glad/gl.h', 'r', encoding='utf-8') as source: data = source.read() data = strip_trailing_whitespace(data) diff --git a/kitty/fonts/common.py b/kitty/fonts/common.py index 3a0edc291..25f2885c4 100644 --- a/kitty/fonts/common.py +++ b/kitty/fonts/common.py @@ -59,7 +59,7 @@ else: set_axis_values, set_named_style, ) - def face_from_descriptor(descriptor, font_sz_in_pts = None, dpi_x = None, dpi_y = None): + def face_from_descriptor(descriptor: Descriptor, font_sz_in_pts: float | None = None, dpi_x: float | None = None, dpi_y: float | None = None) -> Face: if font_sz_in_pts is not None: descriptor = specialize_font_descriptor(descriptor, font_sz_in_pts, dpi_x, dpi_y) return Face(descriptor=descriptor) diff --git a/kitty/types.py b/kitty/types.py index 6b4e04d76..93aa7c7f9 100644 --- a/kitty/types.py +++ b/kitty/types.py @@ -4,7 +4,7 @@ from collections.abc import Callable, Iterator, Mapping, Sequence from enum import Enum from functools import update_wrapper -from typing import TYPE_CHECKING, Any, Generic, Literal, NamedTuple, TypedDict, TypeVar, Union +from typing import TYPE_CHECKING, Any, Literal, NamedTuple, TypedDict, TypeVar, Union, cast if TYPE_CHECKING: from kitty.fast_data_types import SingleKey @@ -151,43 +151,41 @@ class WindowSystemMouseEvent(NamedTuple): ConvertibleToNumbers = Union[str, bytes, int, float] +class sentinel_type: + pass +sentinel = sentinel_type() class AsyncResponse: pass -if TYPE_CHECKING: - class RunOnce(Generic[_T]): +class RunOnce[T]: - def __init__(self, func: Callable[[], _T]): ... - def __call__(self) -> _T: ... - def set_override(self, val: _T) -> None: ... - def clear_override(self) -> None: ... - def clear_cached(self) -> None: ... -else: - class RunOnce: + def __init__(self, f: Callable[[], T]) -> None: + self._override: T | sentinel_type = sentinel + self._cached_result: T | sentinel_type = sentinel + update_wrapper(self, f) - def __init__(self, f): - self._override = RunOnce - self._cached_result = RunOnce - update_wrapper(self, f) - - def __call__(self): - if self._override is not RunOnce: + if TYPE_CHECKING: + def __call__(self) -> T: + return cast(T, 1) + else: + def __call__(self) -> T: + if self._override is not sentinel: return self._override - if self._cached_result is RunOnce: + if self._cached_result is sentinel: self._cached_result = self.__wrapped__() return self._cached_result - def clear_cached(self): - self._cached_result = RunOnce + def clear_cached(self) -> None: + self._cached_result = sentinel - def set_override(self, val): - self._override = val + def set_override(self, val: T) -> None: + self._override = val - def clear_override(self): - self._override = RunOnce + def clear_override(self) -> None: + self._override = sentinel def run_once(f: Callable[[], _T]) -> 'RunOnce[_T]': diff --git a/pyproject.toml b/pyproject.toml index 9a7b994bb..d59a6e759 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,10 +25,21 @@ line-length = 160 [tool.ruff.lint] select = ['E', 'F', 'I', 'RUF100'] +# Select the flake8-annotations ruleset for marking untyped function +extend-select = ["ANN"] +ignore = ["ANN401"] # allow typing.Any + +[tool.ruff.lint.flake8-annotations] +# Allow omitting '-> None' on __init__ if arguments are typed +mypy-init-return = true [tool.ruff.lint.per-file-ignores] "kitty/options/types.py" = ["E501"] "kitty/options/parse.py" = ["E501"] +"shell-integration/ssh/bootstrap.py" = ["ANN"] +"kitty_tests/*.py" = ["ANN"] +"bypy/*" = ["ANN"] +"3rdparty/*" = ["ANN"] [tool.ruff.lint.isort] detect-same-package = true