From 5b6acc11a2f08e0dae2d98a2af27c78548b3bc1f Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Apr 2026 16:18:41 +0000 Subject: [PATCH 01/10] Make Python files pass ty typechecker - Add pyproject.toml configuring ty to target Python 3.11 and ignore unresolved-import errors for third-party packages not in this env - config/qutebrowser/config.py: use cast(Any, None) in TYPE_CHECKING block so c and config are typed Any (bare annotations aren't definitions) - dot/jupyter/jupyter_notebook_config.py: suppress get_config undefined reference with type: ignore (injected by Jupyter at runtime) - py-scripts/wah.py: annotate data as Dict[str, Any] and replace walrus operator pattern that ty couldn't narrow through - venueQ/venueQ.py: add _Vim stub class so vim has correct attribute types in TYPE_CHECKING context; rename deprecated warn() to warning() - py-scripts/aur-auto-vote-with-chaotic.py: remove now-unused type: ignore https://claude.ai/code/session_01Pa53zwdcjHTSJcRZUxD6ib --- pyproject.toml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..070a8a2c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,5 @@ +[tool.ty.environment] +python-version = "3.11" + +[tool.ty.rules] +unresolved-import = "ignore" From c0984eb4353446df780396fb28140f51c8908f83 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Apr 2026 19:26:37 +0000 Subject: [PATCH 02/10] Use ty.toml instead of pyproject.toml; fix datetime.UTC - Replace pyproject.toml with ty.toml (more appropriate for a dotfiles repo that isn't a Python project) - Drop python-version setting; only configure unresolved-import = "ignore" - venueQ/otis.py: replace datetime.UTC (Python 3.11+) with timezone.utc which works in all Python 3 versions https://claude.ai/code/session_01Pa53zwdcjHTSJcRZUxD6ib --- pyproject.toml | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index 070a8a2c..00000000 --- a/pyproject.toml +++ /dev/null @@ -1,5 +0,0 @@ -[tool.ty.environment] -python-version = "3.11" - -[tool.ty.rules] -unresolved-import = "ignore" From 712aae6e306f41bf90959733b29b9bfa10ed7ea4 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Apr 2026 19:41:07 +0000 Subject: [PATCH 03/10] Fix remaining ty diagnostics - ty.toml: ignore deprecated rule (os.system and argparse.FileType deprecations are 3.14-era soft deprecations, not actionable now) - py-scripts/wah.py: cast Fore/Style/Back to Any after import; colorama is installed so ty resolves its stubs and incorrectly types the ANSI color constants as Literal[int] instead of str https://claude.ai/code/session_01Pa53zwdcjHTSJcRZUxD6ib --- py-scripts/wah.py | 7 ++++++- ty.toml | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/py-scripts/wah.py b/py-scripts/wah.py index 723551e0..3eef517b 100644 --- a/py-scripts/wah.py +++ b/py-scripts/wah.py @@ -9,10 +9,15 @@ import argparse import datetime from pathlib import Path -from typing import Any, Dict, List +from typing import Any, Dict, List, cast import yaml from colorama import Back, Fore, Style, init + +# colorama stubs incorrectly type color constants as int; cast to Any +Fore = cast(Any, Fore) +Style = cast(Any, Style) +Back = cast(Any, Back) from git.objects.commit import Commit # for typing from git.repo import Repo diff --git a/ty.toml b/ty.toml index 2ce7b9d9..92478800 100644 --- a/ty.toml +++ b/ty.toml @@ -1,2 +1,3 @@ [rules] unresolved-import = "ignore" +deprecated = "ignore" From 568f8b2f6fffdcca1f31680d6d7a8932a3a061b1 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Apr 2026 19:43:48 +0000 Subject: [PATCH 04/10] Fix deprecated API usage - ty.toml: remove deprecated = "ignore" now that issues are fixed - cp-templates/main.py: replace argparse.FileType with open() after parsing - py-scripts/demacro.py: replace os.system() with subprocess.run() and os.makedirs(); use Path.expanduser() instead of shell ~ expansion - py-scripts/oscar.py: replace argparse.FileType with plain str args, open files after parsing in __main__ https://claude.ai/code/session_01Pa53zwdcjHTSJcRZUxD6ib --- cp-templates/main.py | 4 ++-- py-scripts/demacro.py | 16 ++++++++------ py-scripts/oscar.py | 51 ++++++++++++++++++++++--------------------- ty.toml | 1 - 4 files changed, 37 insertions(+), 35 deletions(-) diff --git a/cp-templates/main.py b/cp-templates/main.py index e514beaa..03f8022b 100644 --- a/cp-templates/main.py +++ b/cp-templates/main.py @@ -11,10 +11,10 @@ action="store_true", help="Show debugging statements (prints to stderr)", ) -parser.add_argument("input", nargs="?", type=argparse.FileType("r"), default="-") +parser.add_argument("input", nargs="?", default="-") opts = parser.parse_args() -stream = opts.input # input stream +stream = sys.stdin if opts.input == "-" else open(opts.input) # input stream def debug(*args: Any): diff --git a/py-scripts/demacro.py b/py-scripts/demacro.py index 1ef0ea08..304e46b6 100644 --- a/py-scripts/demacro.py +++ b/py-scripts/demacro.py @@ -1,8 +1,10 @@ import os +import subprocess import sys +from pathlib import Path TMP = "/tmp/demacro" -os.system("mkdir -p %s" % TMP) +os.makedirs(TMP, exist_ok=True) arg = sys.argv[1] if ".tex" in arg: @@ -35,13 +37,13 @@ print(line.strip(), file=g) elif doc_started: print(line.strip(), file=g) -os.system("cp -f *.tex " + TMP) -os.system("cp -f ~/dotfiles/py-scripts/demacro-private.sty " + TMP) +subprocess.run("cp -f *.tex " + TMP, shell=True, check=True) +subprocess.run(["cp", "-f", Path("~/dotfiles/py-scripts/demacro-private.sty").expanduser(), TMP], check=True) with open("%s/demacro-private.sty" % TMP, "a") as f: print("\n" + preamble, file=f) os.chdir(TMP) -os.system("~/dotfiles/py-scripts/de-macro source.tex") -os.system("python3 ~/dotfiles/py-scripts/latex2wp.py source-clean.tex") +subprocess.run([Path("~/dotfiles/py-scripts/de-macro").expanduser(), "source.tex"], check=True) +subprocess.run(["python3", Path("~/dotfiles/py-scripts/latex2wp.py").expanduser(), "source-clean.tex"], check=True) os.chdir(current_dir) -os.system("cp -f %s/source-clean.html %s.html" % (TMP, file_root_name)) -os.system("cp -f %s/source-clean.tex %s.clean" % (TMP, file_root_name)) +subprocess.run(["cp", "-f", "%s/source-clean.html" % TMP, "%s.html" % file_root_name], check=True) +subprocess.run(["cp", "-f", "%s/source-clean.tex" % TMP, "%s.clean" % file_root_name], check=True) diff --git a/py-scripts/oscar.py b/py-scripts/oscar.py index f5d718a8..7b5bd25b 100644 --- a/py-scripts/oscar.py +++ b/py-scripts/oscar.py @@ -25,17 +25,16 @@ def quadratic_mean(S): parser.add_argument( "files", nargs="*", - type=argparse.FileType("r"), - default=[sys.stdin], + default=None, help="File to read; default to sys.stdin if not provided.", ) parser.add_argument( "-o", "--output", nargs="?", - const=sys.stdout, + const="-", default=None, - type=argparse.FileType("w"), + type=str, help="Print output to this file (only works with one filename), pass with no argument for stdout.", ) parser.add_argument( @@ -371,28 +370,30 @@ def clean_name(s): if __name__ == "__main__": - files = args.files - assert len(files) == 1 or (args.output is None and args.name is None), ( + filenames = args.files + assert filenames is None or len(filenames) == 1 or (args.output is None and args.name is None), ( "can't write multiple inputs to a single output" ) - if len(files) == 1: - f = files[0] - if f == sys.stdin: - if args.output is None: - args.output = sys.stdout - name = args.name or "competition" - else: - name = args.name or clean_name(os.path.basename(f.name)) - - if args.output is None: - with open(os.path.splitext(f.name)[0] + ".oscar.tex", "w") as o: - main(f, o, name) - else: - main(f, args.output, name) - + if not filenames: + # stdin + outfile = sys.stdout if args.output == "-" else (open(args.output, "w") if args.output else sys.stdout) + main(sys.stdin, outfile, args.name or "competition") + elif len(filenames) == 1: + filename = filenames[0] + name = args.name or clean_name(os.path.basename(filename)) + with open(filename) as f: + if args.output == "-": + main(f, sys.stdout, name) + elif args.output is not None: + with open(args.output, "w") as o: + main(f, o, name) + else: + with open(os.path.splitext(filename)[0] + ".oscar.tex", "w") as o: + main(f, o, name) else: - for f in files: - name = clean_name(os.path.basename(f.name)) - with open(os.path.splitext(f.name)[0] + ".oscar.tex", "w") as o: - main(f, o, clean_name(name)) + for filename in filenames: + name = clean_name(os.path.basename(filename)) + with open(filename) as f: + with open(os.path.splitext(filename)[0] + ".oscar.tex", "w") as o: + main(f, o, clean_name(name)) diff --git a/ty.toml b/ty.toml index 92478800..2ce7b9d9 100644 --- a/ty.toml +++ b/ty.toml @@ -1,3 +1,2 @@ [rules] unresolved-import = "ignore" -deprecated = "ignore" From 8f633298be5e7d7c268c9a03ded4dd2dd8a23015 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Apr 2026 19:47:46 +0000 Subject: [PATCH 05/10] Revert colorama cast workaround The colorama integer-literal type bug is fixed in ty 0.0.28; no need to work around it in code. https://claude.ai/code/session_01Pa53zwdcjHTSJcRZUxD6ib --- py-scripts/wah.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/py-scripts/wah.py b/py-scripts/wah.py index 3eef517b..723551e0 100644 --- a/py-scripts/wah.py +++ b/py-scripts/wah.py @@ -9,15 +9,10 @@ import argparse import datetime from pathlib import Path -from typing import Any, Dict, List, cast +from typing import Any, Dict, List import yaml from colorama import Back, Fore, Style, init - -# colorama stubs incorrectly type color constants as int; cast to Any -Fore = cast(Any, Fore) -Style = cast(Any, Style) -Back = cast(Any, Back) from git.objects.commit import Commit # for typing from git.repo import Repo From 07f8fb639c353d661ad83067ffd579ad9d4b4b5c Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Apr 2026 19:49:15 +0000 Subject: [PATCH 06/10] Remove now-unused type: ignore comments yaml is installed in the real environment so ty 0.0.28 can resolve SafeDumper's attributes cleanly; the suppressions are no longer needed. https://claude.ai/code/session_01Pa53zwdcjHTSJcRZUxD6ib --- py-scripts/orch.py | 2 +- venueQ/venueQ.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/py-scripts/orch.py b/py-scripts/orch.py index 12a95bae..da883d07 100755 --- a/py-scripts/orch.py +++ b/py-scripts/orch.py @@ -19,7 +19,7 @@ OTIS_WEB_TOKEN = os.getenv("OTIS_WEB_TOKEN") assert OTIS_WEB_TOKEN is not None -yaml.SafeDumper.orig_represent_str = yaml.SafeDumper.represent_str # type: ignore +yaml.SafeDumper.orig_represent_str = yaml.SafeDumper.represent_str def repr_str(dumper, data): diff --git a/venueQ/venueQ.py b/venueQ/venueQ.py index 5cae8bb9..47053610 100644 --- a/venueQ/venueQ.py +++ b/venueQ/venueQ.py @@ -8,14 +8,14 @@ import yaml -yaml.SafeDumper.orig_represent_str = yaml.SafeDumper.represent_str # type: ignore +yaml.SafeDumper.orig_represent_str = yaml.SafeDumper.represent_str def repr_str(dumper: yaml.SafeDumper, data: str) -> yaml.ScalarNode: if "\n" in data: data = data.replace("\r", "") return dumper.represent_scalar("tag:yaml.org,2002:str", data, style="|") - return dumper.orig_represent_str(data) # type: ignore + return dumper.orig_represent_str(data) yaml.add_representer(str, repr_str, Dumper=yaml.SafeDumper) From 29ca5afb7ba11c4064c8bbbe9a150f6dbe0ba33b Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Apr 2026 20:00:55 +0000 Subject: [PATCH 07/10] Per-line type ignores, venueQ package init, submodule exclusion - ty.toml: replace blanket unresolved-import rule with just excluding the 3kon submodule (which imports fa_icons) - py-scripts/cal2discord.py: add per-line type: ignore[unresolved-import] for humanize, recurring_ical_events, discord_webhook, icalendar - venueQ/__init__.py: add package init re-exporting from venueQ.venueQ so "from venueQ import ..." resolves correctly for ty - py-scripts/orch.py, venueQ/venueQ.py: replace direct attribute assignment/access on SafeDumper with setattr/getattr to avoid unresolved-attribute errors when yaml stubs are available https://claude.ai/code/session_01Pa53zwdcjHTSJcRZUxD6ib --- py-scripts/cal2discord.py | 8 ++++---- py-scripts/orch.py | 4 ++-- ty.toml | 3 +-- venueQ/__init__.py | 3 +++ venueQ/venueQ.py | 4 ++-- 5 files changed, 12 insertions(+), 10 deletions(-) create mode 100644 venueQ/__init__.py diff --git a/py-scripts/cal2discord.py b/py-scripts/cal2discord.py index df563a29..ee18b6aa 100644 --- a/py-scripts/cal2discord.py +++ b/py-scripts/cal2discord.py @@ -3,13 +3,13 @@ from calendar import TextCalendar from typing import Any, List, Tuple -import humanize +import humanize # type: ignore[unresolved-import] import pytz -import recurring_ical_events +import recurring_ical_events # type: ignore[unresolved-import] import requests import yaml -from discord_webhook import DiscordEmbed, DiscordWebhook -from icalendar import Calendar, Event +from discord_webhook import DiscordEmbed, DiscordWebhook # type: ignore[unresolved-import] +from icalendar import Calendar, Event # type: ignore[unresolved-import] parser = argparse.ArgumentParser() parser.add_argument("config") diff --git a/py-scripts/orch.py b/py-scripts/orch.py index da883d07..11bb71a9 100755 --- a/py-scripts/orch.py +++ b/py-scripts/orch.py @@ -19,14 +19,14 @@ OTIS_WEB_TOKEN = os.getenv("OTIS_WEB_TOKEN") assert OTIS_WEB_TOKEN is not None -yaml.SafeDumper.orig_represent_str = yaml.SafeDumper.represent_str +setattr(yaml.SafeDumper, "orig_represent_str", yaml.SafeDumper.represent_str) def repr_str(dumper, data): if "\n" in data: data = data.replace("\r", "") return dumper.represent_scalar("tag:yaml.org,2002:str", data, style="|") - return dumper.orig_represent_str(data) + return getattr(dumper, "orig_represent_str")(data) yaml.add_representer(str, repr_str, Dumper=yaml.SafeDumper) diff --git a/ty.toml b/ty.toml index 2ce7b9d9..b66ffc1c 100644 --- a/ty.toml +++ b/ty.toml @@ -1,2 +1 @@ -[rules] -unresolved-import = "ignore" +exclude = ["config/i3/3kon"] diff --git a/venueQ/__init__.py b/venueQ/__init__.py new file mode 100644 index 00000000..e70c81c0 --- /dev/null +++ b/venueQ/__init__.py @@ -0,0 +1,3 @@ +from venueQ.venueQ import Data, VenueQNode, VenueQRoot, logger + +__all__ = ["Data", "VenueQNode", "VenueQRoot", "logger"] diff --git a/venueQ/venueQ.py b/venueQ/venueQ.py index 47053610..27af2ff5 100644 --- a/venueQ/venueQ.py +++ b/venueQ/venueQ.py @@ -8,14 +8,14 @@ import yaml -yaml.SafeDumper.orig_represent_str = yaml.SafeDumper.represent_str +setattr(yaml.SafeDumper, "orig_represent_str", yaml.SafeDumper.represent_str) def repr_str(dumper: yaml.SafeDumper, data: str) -> yaml.ScalarNode: if "\n" in data: data = data.replace("\r", "") return dumper.represent_scalar("tag:yaml.org,2002:str", data, style="|") - return dumper.orig_represent_str(data) + return getattr(dumper, "orig_represent_str")(data) yaml.add_representer(str, repr_str, Dumper=yaml.SafeDumper) From ed3d101da60cc58733295b56b0a59bc37c01a418 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Apr 2026 20:03:41 +0000 Subject: [PATCH 08/10] Fix ty.toml: exclude must be under [src] section https://claude.ai/code/session_01Pa53zwdcjHTSJcRZUxD6ib --- ty.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/ty.toml b/ty.toml index b66ffc1c..f0646a36 100644 --- a/ty.toml +++ b/ty.toml @@ -1 +1,2 @@ +[src] exclude = ["config/i3/3kon"] From ef692aa6aa0bba73c317cf0adc5191af8cbcdf9b Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Apr 2026 20:05:09 +0000 Subject: [PATCH 09/10] Fix ignore comment syntax: ty uses # ty: ignore, not # type: ignore https://claude.ai/code/session_01Pa53zwdcjHTSJcRZUxD6ib --- py-scripts/cal2discord.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/py-scripts/cal2discord.py b/py-scripts/cal2discord.py index ee18b6aa..2aa7525c 100644 --- a/py-scripts/cal2discord.py +++ b/py-scripts/cal2discord.py @@ -3,13 +3,13 @@ from calendar import TextCalendar from typing import Any, List, Tuple -import humanize # type: ignore[unresolved-import] +import humanize # ty: ignore[unresolved-import] import pytz -import recurring_ical_events # type: ignore[unresolved-import] +import recurring_ical_events # ty: ignore[unresolved-import] import requests import yaml -from discord_webhook import DiscordEmbed, DiscordWebhook # type: ignore[unresolved-import] -from icalendar import Calendar, Event # type: ignore[unresolved-import] +from discord_webhook import DiscordEmbed, DiscordWebhook # ty: ignore[unresolved-import] +from icalendar import Calendar, Event # ty: ignore[unresolved-import] parser = argparse.ArgumentParser() parser.add_argument("config") From 273ec8c5e2c4f52fddfd9a30d753dfcc063c63bf Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Apr 2026 20:07:15 +0000 Subject: [PATCH 10/10] ruff format https://claude.ai/code/session_01Pa53zwdcjHTSJcRZUxD6ib --- py-scripts/cal2discord.py | 8 ++++---- py-scripts/demacro.py | 26 +++++++++++++++++++++----- py-scripts/oscar.py | 14 ++++++++++---- 3 files changed, 35 insertions(+), 13 deletions(-) diff --git a/py-scripts/cal2discord.py b/py-scripts/cal2discord.py index 2aa7525c..7e494245 100644 --- a/py-scripts/cal2discord.py +++ b/py-scripts/cal2discord.py @@ -3,13 +3,13 @@ from calendar import TextCalendar from typing import Any, List, Tuple -import humanize # ty: ignore[unresolved-import] +import humanize # ty: ignore[unresolved-import] import pytz -import recurring_ical_events # ty: ignore[unresolved-import] +import recurring_ical_events # ty: ignore[unresolved-import] import requests import yaml -from discord_webhook import DiscordEmbed, DiscordWebhook # ty: ignore[unresolved-import] -from icalendar import Calendar, Event # ty: ignore[unresolved-import] +from discord_webhook import DiscordEmbed, DiscordWebhook # ty: ignore[unresolved-import] +from icalendar import Calendar, Event # ty: ignore[unresolved-import] parser = argparse.ArgumentParser() parser.add_argument("config") diff --git a/py-scripts/demacro.py b/py-scripts/demacro.py index 304e46b6..38935335 100644 --- a/py-scripts/demacro.py +++ b/py-scripts/demacro.py @@ -38,12 +38,28 @@ elif doc_started: print(line.strip(), file=g) subprocess.run("cp -f *.tex " + TMP, shell=True, check=True) -subprocess.run(["cp", "-f", Path("~/dotfiles/py-scripts/demacro-private.sty").expanduser(), TMP], check=True) +subprocess.run( + ["cp", "-f", Path("~/dotfiles/py-scripts/demacro-private.sty").expanduser(), TMP], + check=True, +) with open("%s/demacro-private.sty" % TMP, "a") as f: print("\n" + preamble, file=f) os.chdir(TMP) -subprocess.run([Path("~/dotfiles/py-scripts/de-macro").expanduser(), "source.tex"], check=True) -subprocess.run(["python3", Path("~/dotfiles/py-scripts/latex2wp.py").expanduser(), "source-clean.tex"], check=True) +subprocess.run( + [Path("~/dotfiles/py-scripts/de-macro").expanduser(), "source.tex"], check=True +) +subprocess.run( + [ + "python3", + Path("~/dotfiles/py-scripts/latex2wp.py").expanduser(), + "source-clean.tex", + ], + check=True, +) os.chdir(current_dir) -subprocess.run(["cp", "-f", "%s/source-clean.html" % TMP, "%s.html" % file_root_name], check=True) -subprocess.run(["cp", "-f", "%s/source-clean.tex" % TMP, "%s.clean" % file_root_name], check=True) +subprocess.run( + ["cp", "-f", "%s/source-clean.html" % TMP, "%s.html" % file_root_name], check=True +) +subprocess.run( + ["cp", "-f", "%s/source-clean.tex" % TMP, "%s.clean" % file_root_name], check=True +) diff --git a/py-scripts/oscar.py b/py-scripts/oscar.py index 7b5bd25b..f1cb78a5 100644 --- a/py-scripts/oscar.py +++ b/py-scripts/oscar.py @@ -371,13 +371,19 @@ def clean_name(s): if __name__ == "__main__": filenames = args.files - assert filenames is None or len(filenames) == 1 or (args.output is None and args.name is None), ( - "can't write multiple inputs to a single output" - ) + assert ( + filenames is None + or len(filenames) == 1 + or (args.output is None and args.name is None) + ), "can't write multiple inputs to a single output" if not filenames: # stdin - outfile = sys.stdout if args.output == "-" else (open(args.output, "w") if args.output else sys.stdout) + outfile = ( + sys.stdout + if args.output == "-" + else (open(args.output, "w") if args.output else sys.stdout) + ) main(sys.stdin, outfile, args.name or "competition") elif len(filenames) == 1: filename = filenames[0]