Merge pull request 'Qubasa-hsjobeki/bump-nixpkgs' (#4205) from Qubasa-hsjobeki/bump-nixpkgs into main

Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4205
This commit is contained in:
Mic92
2025-07-04 16:47:23 +00:00
26 changed files with 114 additions and 50 deletions

6
flake.lock generated
View File

@@ -164,10 +164,10 @@
"nixpkgs": {
"locked": {
"lastModified": 315532800,
"narHash": "sha256-VgDAFPxHNhCfC7rI5I5wFqdiVJBH43zUefVo8hwo7cI=",
"rev": "41da1e3ea8e23e094e5e3eeb1e6b830468a7399e",
"narHash": "sha256-0HRxGUoOMtOYnwlMWY0AkuU88WHaI3Q5GEILmsWpI8U=",
"rev": "a48741b083d4f36dd79abd9f760c84da6b4dc0e5",
"type": "tarball",
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-25.11pre814815.41da1e3ea8e2/nixexprs.tar.xz"
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-25.11pre823094.a48741b083d4/nixexprs.tar.xz"
},
"original": {
"type": "tarball",

View File

@@ -15,7 +15,7 @@ find = {}
[tool.setuptools.package-data]
test_driver = ["py.typed"]
[tool.mypy]
python_version = "3.12"
python_version = "3.13"
warn_redundant_casts = true
disallow_untyped_calls = true
disallow_untyped_defs = true

View File

@@ -1,4 +1,3 @@
# ruff: noqa: N801
import gi
gi.require_version("Gtk", "4.0")

View File

@@ -30,7 +30,7 @@ norecursedirs = "tests/helpers"
markers = ["impure"]
[tool.mypy]
python_version = "3.12"
python_version = "3.13"
warn_redundant_casts = true
disallow_untyped_calls = true
disallow_untyped_defs = true

View File

@@ -7,7 +7,7 @@ import pytest
@pytest.fixture(scope="session")
def wayland_compositor() -> Generator[Popen, None, None]:
def wayland_compositor() -> Generator[Popen]:
# Start the Wayland compositor (e.g., Weston)
# compositor = Popen(["weston", "--backend=headless-backend.so"])
compositor = Popen(["weston"])
@@ -20,7 +20,7 @@ GtkProc = NewType("GtkProc", Popen)
@pytest.fixture
def app() -> Generator[GtkProc, None, None]:
def app() -> Generator[GtkProc]:
cmd = [sys.executable, "-m", "clan_app"]
print(f"Running: {cmd}")
rapp = Popen(

View File

@@ -5,9 +5,9 @@ import shutil
import subprocess as sp
import tempfile
from collections import defaultdict
from collections.abc import Callable, Iterator
from collections.abc import Iterator
from pathlib import Path
from typing import Any, NamedTuple
from typing import NamedTuple
import pytest
from clan_cli.tests import age_keys
@@ -38,7 +38,12 @@ def def_value() -> defaultdict:
return defaultdict(def_value)
nested_dict: Callable[[], dict[str, Any]] = lambda: defaultdict(def_value)
def nested_dict() -> defaultdict:
"""
Creates a defaultdict that allows for arbitrary levels of nesting.
For example: d['a']['b']['c'] = value
"""
return defaultdict(def_value)
# Substitutes strings in a file.

View File

@@ -1,11 +0,0 @@
from collections import defaultdict
from collections.abc import Callable
from typing import Any
def def_value() -> defaultdict:
return defaultdict(def_value)
# allows defining nested dictionary in a single line
nested_dict: Callable[[], dict[str, Any]] = lambda: defaultdict(def_value)

View File

@@ -4,14 +4,13 @@ import logging
from clan_cli.completions import add_dynamic_completer, complete_machines
from clan_lib.errors import ClanError
from clan_lib.machines.machines import Machine
log = logging.getLogger(__name__)
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .generate import Var
log = logging.getLogger(__name__)
class VarStatus:
def __init__(

View File

@@ -16,14 +16,14 @@ from typing import (
)
from clan_lib.api.util import JSchemaTypeError
from clan_lib.errors import ClanError
from .serde import dataclass_to_dict, from_dict, sanitize_string
log = logging.getLogger(__name__)
from .serde import dataclass_to_dict, from_dict, sanitize_string
__all__ = ["dataclass_to_dict", "from_dict", "sanitize_string"]
from clan_lib.errors import ClanError
T = TypeVar("T")

View File

@@ -146,8 +146,31 @@ def is_union_type(type_hint: type | UnionType) -> bool:
def is_type_in_union(union_type: type | UnionType, target_type: type) -> bool:
if get_origin(union_type) is UnionType:
return any(issubclass(arg, target_type) for arg in get_args(union_type))
# Check for Union from typing module (Union[str, None]) or UnionType (str | None)
if get_origin(union_type) in (Union, UnionType):
args = get_args(union_type)
for arg in args:
# Handle None type specially since it's not a class
if arg is None or arg is type(None):
if target_type is type(None):
return True
# For generic types like dict[str, str], check their origin
elif get_origin(arg) is not None:
if get_origin(arg) == target_type:
return True
# Also check if target_type is a generic with same origin
elif get_origin(target_type) is not None and get_origin(
arg
) == get_origin(target_type):
return True
# For actual classes, use issubclass
elif inspect.isclass(arg) and inspect.isclass(target_type):
if issubclass(arg, target_type):
return True
# For non-class types, use direct comparison
elif arg == target_type:
return True
return False
return union_type == target_type

View File

@@ -8,6 +8,7 @@ import pytest
from clan_lib.api import dataclass_to_dict, from_dict
from clan_lib.errors import ClanError
from clan_lib.machines import machines
from clan_lib.api.serde import is_type_in_union
def test_simple() -> None:
@@ -216,6 +217,44 @@ def test_none_or_string() -> None:
assert checked3 is None
def test_union_with_none_edge_cases() -> None:
"""
Test various union types with None to ensure issubclass() error is avoided.
This specifically tests the fix for the TypeError in is_type_in_union.
"""
# Test basic types with None
assert from_dict(str | None, None) is None
assert from_dict(str | None, "hello") == "hello"
# Test dict with None - this was the specific case that failed
assert from_dict(dict[str, str] | None, None) is None
assert from_dict(dict[str, str] | None, {"key": "value"}) == {"key": "value"}
# Test list with None
assert from_dict(list[str] | None, None) is None
assert from_dict(list[str] | None, ["a", "b"]) == ["a", "b"]
# Test dataclass with None
@dataclass
class TestClass:
value: str
assert from_dict(TestClass | None, None) is None
assert from_dict(TestClass | None, {"value": "test"}) == TestClass(value="test")
# Test Path with None (since it's used in the original failing test)
assert from_dict(Path | None, None) is None
assert from_dict(Path | None, "/home/test") == Path("/home/test")
# Test that the is_type_in_union function works correctly
# This is the core of what was fixed - ensuring None doesn't cause issubclass error
# These should not raise TypeError anymore
assert is_type_in_union(str | None, type(None)) is True
assert is_type_in_union(dict[str, str] | None, type(None)) is True
assert is_type_in_union(list[str] | None, type(None)) is True
assert is_type_in_union(Path | None, type(None)) is True
def test_roundtrip_escape() -> None:
assert from_dict(str, "\n") == "\n"
assert dataclass_to_dict("\n") == "\n"

View File

@@ -179,6 +179,7 @@ def terminate_process_group(process: subprocess.Popen) -> Iterator[None]:
try:
process_group = os.getpgid(process.pid)
except ProcessLookupError:
yield
return
if process_group == os.getpgid(os.getpid()):
msg = "Bug! Refusing to terminate the current process group"

View File

@@ -42,7 +42,10 @@ def delete_machine(machine: Machine) -> None:
# louis@(2025-02-04): clean-up legacy (pre-vars) secrets:
sops_folder = sops_secrets_folder(machine.flake.path)
filter_fn = lambda secret_name: secret_name.startswith(f"{machine.name}-")
def filter_fn(secret_name: str) -> bool:
return secret_name.startswith(f"{machine.name}-")
for secret_name in list_secrets(machine.flake.path, filter_fn):
secret_path = sops_folder / secret_name
changed_paths.append(secret_path)

View File

@@ -1,4 +1,3 @@
# ruff: noqa: SLF001
import ipaddress
import logging
import os

View File

@@ -1,5 +1,6 @@
import argparse
import sys
import re
from dataclasses import dataclass
from pathlib import Path
@@ -118,9 +119,6 @@ def epilog_to_md(text: str) -> str:
return md
import re
def contains_https_link(line: str) -> bool:
pattern = r"https://\S+"
return re.search(pattern, line) is not None

View File

@@ -49,9 +49,12 @@ filterwarnings = "default::ResourceWarning"
python_files = ["test_*.py", "*_test.py"]
[tool.mypy]
python_version = "3.12"
python_version = "3.13"
warn_redundant_casts = true
disallow_untyped_calls = true
disallow_untyped_defs = true
no_implicit_optional = true
exclude = "clan_lib.nixpkgs"
[tool.ruff]
target-version = "py313"

View File

@@ -1,6 +1,6 @@
import logging
from collections.abc import Callable
from typing import Any, Generic, TypeVar
from typing import Any, TypeVar
import gi
@@ -22,7 +22,7 @@ V = TypeVar(
# clan_vm_manager/components/gkvstore.py:21: error: Definition of "newv" in base class "Object" is incompatible with definition in base class "GInterface" [misc]
# clan_vm_manager/components/gkvstore.py:21: error: Definition of "install_properties" in base class "Object" is incompatible with definition in base class "GInterface" [misc]
# clan_vm_manager/components/gkvstore.py:21: error: Definition of "getv" in base class "Object" is incompatible with definition in base class "GInterface" [misc]
class GKVStore(GObject.GObject, Gio.ListModel, Generic[K, V]): # type: ignore[misc]
class GKVStore[K, V: GObject.Object](GObject.GObject, Gio.ListModel): # type: ignore[misc]
"""
A simple key-value store that implements the Gio.ListModel interface, with generic types for keys and values.
Only use self[key] and del self[key] for accessing the items for better performance.

View File

@@ -143,7 +143,7 @@ class VMObject(GObject.Object):
# We use a context manager to create the machine object
# and make sure it is destroyed when the context is exited
@contextmanager
def _create_machine(self) -> Generator[Machine, None, None]:
def _create_machine(self) -> Generator[Machine]:
uri = ClanURI.from_str(
url=str(self.data.flake.flake_url), machine_name=self.data.flake.flake_attr
)

View File

@@ -12,7 +12,7 @@ from gi.repository import Adw, Gio, GObject, Gtk
ListItem = TypeVar("ListItem", bound=GObject.Object)
def create_details_list(
def create_details_list[ListItem: GObject.Object](
model: Gio.ListStore, render_row: Callable[[Gtk.ListBox, ListItem], Gtk.Widget]
) -> Gtk.ListBox:
boxed_list = Gtk.ListBox()

View File

@@ -32,7 +32,7 @@ ListItem = TypeVar("ListItem", bound=GObject.Object)
CustomStore = TypeVar("CustomStore", bound=Gio.ListModel)
def create_boxed_list(
def create_boxed_list[CustomStore: Gio.ListModel, ListItem: GObject.Object](
model: CustomStore,
render_row: Callable[[Gtk.ListBox, ListItem], Gtk.Widget],
) -> Gtk.ListBox:

View File

@@ -30,7 +30,7 @@ norecursedirs = "tests/helpers"
markers = ["impure"]
[tool.mypy]
python_version = "3.12"
python_version = "3.13"
warn_redundant_casts = true
disallow_untyped_calls = true
disallow_untyped_defs = true

View File

@@ -7,7 +7,7 @@ import pytest
@pytest.fixture(scope="session")
def wayland_compositor() -> Generator[Popen, None, None]:
def wayland_compositor() -> Generator[Popen]:
# Start the Wayland compositor (e.g., Weston)
# compositor = Popen(["weston", "--backend=headless-backend.so"])
compositor = Popen(["weston"])
@@ -20,7 +20,7 @@ GtkProc = NewType("GtkProc", Popen)
@pytest.fixture
def app() -> Generator[GtkProc, None, None]:
def app() -> Generator[GtkProc]:
rapp = Popen([sys.executable, "-m", "clan_vm_manager"], text=True)
yield GtkProc(rapp)
# Cleanup: Terminate your application

View File

@@ -1,4 +1,3 @@
# ruff: noqa: RUF001
import argparse
import json
import logging

View File

@@ -26,7 +26,7 @@ addopts = "--durations 5 --color=yes --new-first" # Add --pdb for debugging
norecursedirs = "tests/helpers"
[tool.mypy]
python_version = "3.12"
python_version = "3.13"
warn_redundant_casts = true
disallow_untyped_calls = true
disallow_untyped_defs = true

View File

@@ -1,7 +1,14 @@
{ zerotierone, lib }:
{
zerotierone,
stdenv,
lib,
}:
# halalify zerotierone
zerotierone.overrideAttrs (_old: {
meta = _old.meta // {
zerotierone.overrideAttrs (old: {
# Maybe a sandbox issue?
# zerotierone> [phy] Binding UDP listen socket to 127.0.0.1/60002... FAILED.
doCheck = old.doCheck && !stdenv.hostPlatform.isDarwin;
meta = old.meta // {
license = lib.licenses.apsl20;
};
})

View File

@@ -1,5 +1,5 @@
[tool.mypy]
python_version = "3.12"
python_version = "3.13"
pretty = true
warn_redundant_casts = true
disallow_untyped_calls = true
@@ -8,7 +8,7 @@ no_implicit_optional = true
exclude = "clan_cli.nixpkgs"
[tool.ruff]
target-version = "py311"
target-version = "py313"
line-length = 88
lint.select = [
"A",