Skip to content

Commit 02e6a8f

Browse files
committed
PrefsFile: Deserialize GenericAlias properly (jorio#112)
1 parent aad01b2 commit 02e6a8f

3 files changed

Lines changed: 51 additions & 16 deletions

File tree

gitfourchette/prefsfile.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# -----------------------------------------------------------------------------
2-
# Copyright (C) 2025 Iliyas Jorio.
2+
# Copyright (C) 2026 Iliyas Jorio.
33
# This file is part of GitFourchette, distributed under the GNU GPL v3.
44
# For full terms, see the included LICENSE file.
55
# -----------------------------------------------------------------------------
@@ -10,7 +10,7 @@
1010
import logging
1111
import os
1212
import typing
13-
from types import NoneType, UnionType
13+
from types import NoneType, UnionType, GenericAlias
1414
from typing import Any
1515

1616
from gitfourchette import pycompat # noqa: F401 - StrEnum for Python 3.10
@@ -182,7 +182,7 @@ def encode(o: Any) -> Any:
182182
return o
183183

184184
@staticmethod
185-
def decode(o: Any, dstType: type | UnionType) -> Any:
185+
def decode(o: Any, dstType: type | UnionType | GenericAlias) -> Any:
186186
""" Convert a value coming from a JSON blob to a target type """
187187
construct: typing.Callable[[Any], Any] | None = None
188188

@@ -192,6 +192,10 @@ def decode(o: Any, dstType: type | UnionType) -> Any:
192192
assert len(union) == 2
193193
dstType = next(t for t in union if t is not NoneType)
194194

195+
# Extract generic class from GenericAlias, e.g. list[str] --> list
196+
if type(dstType) is GenericAlias:
197+
dstType = typing.get_origin(dstType)
198+
195199
srcType: type
196200
if dstType is bytes:
197201
srcType = str

gitfourchette/settings.py

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
import enum
99
import logging
1010
import os
11+
from collections.abc import Iterator
1112
from contextlib import suppress
13+
from typing import TypedDict
1214

1315
from gitfourchette import colors
1416
from gitfourchette import pycompat # noqa: F401 - StrEnum for Python 3.10
@@ -239,9 +241,15 @@ def isGitSandboxed(self):
239241
class History(PrefsFile):
240242
_filename = "history.json"
241243

242-
repos: dict = dataclasses.field(default_factory=dict)
243-
cloneHistory: list = dataclasses.field(default_factory=list)
244-
fileDialogPaths: dict = dataclasses.field(default_factory=dict)
244+
class JsonRepo(TypedDict, total=False):
245+
seq: int
246+
length: int
247+
nickname: str
248+
superproject: str
249+
250+
repos: dict[str, JsonRepo] = dataclasses.field(default_factory=dict)
251+
cloneHistory: list[str] = dataclasses.field(default_factory=list)
252+
fileDialogPaths: dict[str, str] = dataclasses.field(default_factory=dict)
245253
startups: int = 0
246254

247255
_maxSeq = -1
@@ -252,12 +260,12 @@ def addRepo(self, path: str):
252260
repo['seq'] = self.drawSequenceNumber()
253261
return repo
254262

255-
def getRepo(self, path: str) -> dict:
263+
def getRepo(self, path: str) -> JsonRepo:
256264
path = os.path.normpath(path)
257265
try:
258266
repo = self.repos[path]
259267
except KeyError:
260-
repo = {}
268+
repo: History.JsonRepo = {}
261269
self.repos[path] = repo
262270
return repo
263271

@@ -274,7 +282,7 @@ def setRepoNickname(self, path: str, nickname: str):
274282
else:
275283
repo.pop('nickname', None)
276284

277-
def getRepoNumCommits(self, path: str):
285+
def getRepoNumCommits(self, path: str) -> int:
278286
repo = self.getRepo(path)
279287
return repo.get('length', 0)
280288

@@ -285,7 +293,7 @@ def setRepoNumCommits(self, path: str, commitCount: int):
285293
else:
286294
repo.pop('length', None)
287295

288-
def getRepoSuperproject(self, path: str):
296+
def getRepoSuperproject(self, path: str) -> str:
289297
repo = self.getRepo(path)
290298
return repo.get('superproject', "")
291299

@@ -296,7 +304,7 @@ def setRepoSuperproject(self, path: str, superprojectPath: str):
296304
else:
297305
repo.pop('superproject', None)
298306

299-
def getRepoTabName(self, path: str):
307+
def getRepoTabName(self, path: str) -> str:
300308
name = self.getRepoNickname(path)
301309

302310
seen = {path}
@@ -321,7 +329,7 @@ def clearRepoHistory(self):
321329
self.repos.clear()
322330
self.invalidateSequenceNumber()
323331

324-
def getRecentRepoPaths(self, n: int, newestFirst=True):
332+
def getRecentRepoPaths(self, n: int, newestFirst=True) -> Iterator[str]:
325333
sortedPaths = (path for path, _ in
326334
sorted(self.repos.items(), key=lambda i: i[1].get('seq', -1), reverse=newestFirst))
327335

@@ -365,10 +373,10 @@ def invalidateSequenceNumber(self):
365373
class Session(PrefsFile):
366374
_filename = "session.json"
367375

368-
tabs : list = dataclasses.field(default_factory=list)
369-
activeTabIndex : int = -1
370-
windowGeometry : bytes = b""
371-
splitterSizes : dict = dataclasses.field(default_factory=dict)
376+
tabs : list[str] = dataclasses.field(default_factory=list)
377+
activeTabIndex : int = -1
378+
windowGeometry : bytes = b""
379+
splitterSizes : dict[str, list[int]] = dataclasses.field(default_factory=dict)
372380

373381

374382
# Initialize default prefs and history.

test/test_gitfourchette.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -883,3 +883,26 @@ def testDiffHeader(tempDir, mainWindow):
883883
rw.jump(NavLocator.inUnstaged("hello.txt"), check=True)
884884
assert rw.diffArea.diffView.isVisible()
885885
assert findTextInWidget(rw.diffArea.diffHeader, "hello.txt")
886+
887+
888+
def testPrefsFileGenericAliases(tempDir, mainWindow):
889+
from gitfourchette import settings
890+
891+
assert settings.prefs.dontShowAgain == []
892+
settings.prefs.dontShowAgain.append("NoFastForwardNecessary")
893+
settings.prefs.setDirty()
894+
settings.prefs.write()
895+
settings.prefs.reset()
896+
settings.prefs.load()
897+
assert settings.prefs.dontShowAgain == ["NoFastForwardNecessary"]
898+
899+
assert settings.history.cloneHistory == []
900+
assert not settings.history.getRepoNickname("/tmp/hello", strict=True)
901+
settings.history.cloneHistory.append("https://github.com/jorio/gitfourchette")
902+
settings.history.setRepoNickname("/tmp/hello", "HelloWorld")
903+
settings.history.setDirty()
904+
settings.history.write()
905+
settings.history.reset()
906+
settings.history.load()
907+
assert settings.history.cloneHistory == ["https://github.com/jorio/gitfourchette"]
908+
assert settings.history.getRepoNickname("/tmp/hello", strict=True) == "HelloWorld"

0 commit comments

Comments
 (0)