From 9773f55b381693c0f36832c5ef44a4d645e1c740 Mon Sep 17 00:00:00 2001
From: Oscar Gustafsson <oscar.gustafsson@gmail.com>
Date: Sat, 10 May 2025 13:56:43 +0200
Subject: [PATCH] Use pathlib more

---
 b_asic/GUI/drag_button.py           | 13 +++---
 b_asic/GUI/main_window.py           | 24 +++++------
 b_asic/codegen/testbench/test.py    |  8 ++--
 b_asic/logger.py                    |  4 +-
 b_asic/resources.py                 |  5 ++-
 b_asic/save_load_structure.py       |  3 +-
 b_asic/scheduler_gui/compile.py     |  6 +--
 b_asic/scheduler_gui/main_window.py | 23 +++++-----
 test/conftest.py                    | 11 ++---
 test/unit/test_sfg.py               | 65 +++++++++++++++--------------
 10 files changed, 84 insertions(+), 78 deletions(-)

diff --git a/b_asic/GUI/drag_button.py b/b_asic/GUI/drag_button.py
index 48179611..c83325ec 100644
--- a/b_asic/GUI/drag_button.py
+++ b/b_asic/GUI/drag_button.py
@@ -4,7 +4,7 @@ B-ASIC Drag Button Module.
 Contains a GUI class for drag buttons.
 """
 
-import os.path
+from pathlib import Path
 from typing import TYPE_CHECKING
 
 from qtpy.QtCore import QSize, Qt, Signal
@@ -177,13 +177,12 @@ class DragButton(QPushButton):
             " border-style: solid;        border-color: black;"
             " border-width: 2px"
         )
-        path_to_image = os.path.join(
-            os.path.dirname(__file__),
-            "operation_icons",
-            f"{self.operation.type_name().lower()}"
-            f"{'_grey.png' if self.pressed else '.png'}",
+        path_to_image = (
+            Path(__file__).parent
+            / "operation_icons"
+            / f"{self.operation.type_name().lower()}{'_grey' if self.pressed else ''}.png"
         )
-        self.setIcon(QIcon(path_to_image))
+        self.setIcon(QIcon(str(path_to_image)))
         self.setIconSize(QSize(MINBUTTONSIZE, MINBUTTONSIZE))
 
     def is_flipped(self) -> bool:
diff --git a/b_asic/GUI/main_window.py b/b_asic/GUI/main_window.py
index 126cdd36..10280e73 100644
--- a/b_asic/GUI/main_window.py
+++ b/b_asic/GUI/main_window.py
@@ -6,11 +6,11 @@ This file opens the main SFG editor window of the GUI for B-ASIC when run.
 
 import importlib.util
 import logging
-import os
 import sys
 import webbrowser
 from collections import deque
 from collections.abc import Sequence
+from pathlib import Path
 from types import ModuleType
 from typing import TYPE_CHECKING, cast
 
@@ -285,7 +285,7 @@ class SFGMainWindow(QMainWindow):
             )
 
         try:
-            with open(module, "w+") as file_obj:
+            with Path(module).open("w+") as file_obj:
                 file_obj.write(
                     sfg_to_python(sfg, suffix=f"positions = {operation_positions}")
                 )
@@ -710,18 +710,18 @@ class SFGMainWindow(QMainWindow):
             attr_button.add_ports()
             self._ports[attr_button] = attr_button.port_list
 
-            icon_path = os.path.join(
-                os.path.dirname(__file__),
-                "operation_icons",
-                f"{op.type_name().lower()}.png",
+            icon_path = (
+                Path(__file__).parent
+                / "operation_icons"
+                / f"{op.type_name().lower()}.png"
             )
-            if not os.path.exists(icon_path):
-                icon_path = os.path.join(
-                    os.path.dirname(__file__),
-                    "operation_icons",
-                    "custom_operation.png",
+
+            print(icon_path)
+            if not Path(icon_path).exists():
+                icon_path = (
+                    Path(__file__).parent / "operation_icons" / "custom_operation.png"
                 )
-            attr_button.setIcon(QIcon(icon_path))
+            attr_button.setIcon(QIcon(str(icon_path)))
             attr_button.setIconSize(QSize(MINBUTTONSIZE, MINBUTTONSIZE))
             attr_button.setToolTip("No SFG")
             attr_button.setStyleSheet(
diff --git a/b_asic/codegen/testbench/test.py b/b_asic/codegen/testbench/test.py
index 3e673879..708149e1 100755
--- a/b_asic/codegen/testbench/test.py
+++ b/b_asic/codegen/testbench/test.py
@@ -3,15 +3,17 @@ B-ASIC Codegen Testing Module.
 """
 #!/usr/bin/env python3
 
-from os.path import abspath, dirname
+from pathlib import Path
 from sys import argv
 
 from vunit import VUnit
 
 # Absolute path of the testbench directory
-testbench_path = dirname(abspath(__file__))
+testbench_path = Path(__file__).resolve().parent
 
-vu = VUnit.from_argv(argv=["--output-path", f"{testbench_path}/vunit_out"] + argv[1:])
+vu = VUnit.from_argv(
+    argv=["--output-path", str(testbench_path / "vunit_out")] + argv[1:]
+)
 
 lib = vu.add_library("lib")
 lib.add_source_files(
diff --git a/b_asic/logger.py b/b_asic/logger.py
index 986c1d50..92186852 100644
--- a/b_asic/logger.py
+++ b/b_asic/logger.py
@@ -49,9 +49,9 @@ To log uncaught exceptions, implement the following in your program.
 """
 
 import logging
-import os
 import sys
 from logging import Logger
+from pathlib import Path
 from types import TracebackType
 from typing import Literal
 
@@ -113,7 +113,7 @@ def getLogger(
     if logger.name == "scheduler-gui.log":
         logger.info(
             "Running: %s %s",
-            os.path.basename(sys.argv[0]),
+            Path(sys.argv[0]).name,
             " ".join(sys.argv[1:]),
         )
 
diff --git a/b_asic/resources.py b/b_asic/resources.py
index e8531cb3..a7e1fb6d 100644
--- a/b_asic/resources.py
+++ b/b_asic/resources.py
@@ -12,6 +12,7 @@ from collections import Counter, defaultdict
 from collections.abc import Iterable, Iterator
 from functools import reduce
 from math import floor, log2
+from pathlib import Path
 from typing import TYPE_CHECKING, Literal, TypeVar, Union
 
 import matplotlib.pyplot as plt
@@ -2211,7 +2212,7 @@ class ProcessCollection:
                     "both or none of adr_mux_size and adr_pipe_depth needs to be set"
                 )
 
-        with open(filename, "w") as f:
+        with Path(filename).open("w") as f:
             from b_asic.codegen.vhdl import architecture, common, entity
 
             common.b_asic_preamble(f)
@@ -2335,7 +2336,7 @@ class ProcessCollection:
         # Create the forward-backward table
         forward_backward_table = _ForwardBackwardTable(self)
 
-        with open(filename, "w") as f:
+        with Path(filename).open("w") as f:
             from b_asic.codegen.vhdl import architecture, common, entity
 
             common.b_asic_preamble(f)
diff --git a/b_asic/save_load_structure.py b/b_asic/save_load_structure.py
index 34d84498..b283a8d5 100644
--- a/b_asic/save_load_structure.py
+++ b/b_asic/save_load_structure.py
@@ -7,6 +7,7 @@ stored as files.
 
 from datetime import datetime
 from inspect import signature
+from pathlib import Path
 from typing import cast
 
 from b_asic.graph_component import GraphComponent
@@ -152,7 +153,7 @@ def python_to_sfg(path: str) -> tuple[SFG, dict[str, tuple[int, int]]]:
     """
     local_vars = {}
 
-    with open(path) as file:
+    with Path(path).open() as file:
         code = compile(file.read(), path, "exec")
         exec(code, globals(), local_vars)
 
diff --git a/b_asic/scheduler_gui/compile.py b/b_asic/scheduler_gui/compile.py
index 632ca4e1..17d8380d 100644
--- a/b_asic/scheduler_gui/compile.py
+++ b/b_asic/scheduler_gui/compile.py
@@ -53,11 +53,11 @@ def replace_qt_bindings(filename: str) -> None:
     filename : str
         The name of the file to replace bindings in.
     """
-    with open(f"{filename}") as file:
+    with Path(filename).open() as file:
         filedata = file.read()
         filedata = filedata.replace("from PyQt6", "from qtpy")
         filedata = filedata.replace("from PySide6", "from qtpy")
-    with open(f"{filename}", "w") as file:
+    with Path(filename).open("w") as file:
         file.write(filedata)
 
 
@@ -159,7 +159,7 @@ def compile_ui(*filenames: str) -> None:
         if uic.PYQT6:
             from qtpy.uic import compileUi
 
-            with open(outfile, "w") as ofile:
+            with Path(outfile).open("w") as ofile:
                 compileUi(filename, ofile)
 
         elif uic.PYSIDE6:
diff --git a/b_asic/scheduler_gui/main_window.py b/b_asic/scheduler_gui/main_window.py
index ef12541d..d599eca5 100644
--- a/b_asic/scheduler_gui/main_window.py
+++ b/b_asic/scheduler_gui/main_window.py
@@ -15,6 +15,7 @@ import sys
 import webbrowser
 from collections import defaultdict, deque
 from importlib.machinery import SourceFileLoader
+from pathlib import Path
 from typing import TYPE_CHECKING, ClassVar, cast, overload
 
 # Qt/qtpy
@@ -119,6 +120,7 @@ class ScheduleMainWindow(QMainWindow, Ui_MainWindow):
     _changed_operation_colors: dict[str, QColor]
     _recent_files_actions: list[QAction]
     _recent_file_paths: deque[str]
+    _file_name: str | None
 
     def __init__(self) -> None:
         """Initialize Scheduler-GUI."""
@@ -331,9 +333,9 @@ class ScheduleMainWindow(QMainWindow, Ui_MainWindow):
             QStandardPaths.standardLocations(QStandardPaths.HomeLocation)[0],
             str,
         )
-        if not os.path.exists(last_file):  # if filename does not exist
-            last_file = os.path.dirname(last_file) + "/"
-            if not os.path.exists(last_file):  # if path does not exist
+        if not Path(last_file).exists():  # if filename does not exist
+            last_file = Path(last_file).parent
+            if not last_file.exists():  # if path does not exist
                 last_file = QStandardPaths.standardLocations(
                     QStandardPaths.HomeLocation
                 )[0]
@@ -381,16 +383,15 @@ class ScheduleMainWindow(QMainWindow, Ui_MainWindow):
         )
 
         if not schedule_obj_list:  # return if no Schedule objects in script
+            filename = Path(abs_path_filename).name
             QMessageBox.warning(
                 self,
                 self.tr("File not found"),
-                self.tr("Cannot find any Schedule object in file {}").format(
-                    os.path.basename(abs_path_filename)
-                ),
+                self.tr("Cannot find any Schedule object in file {}").format(filename),
             )
             log.info(
                 "Cannot find any Schedule object in file %s",
-                os.path.basename(abs_path_filename),
+                filename,
             )
             del module
             return
@@ -481,7 +482,7 @@ class ScheduleMainWindow(QMainWindow, Ui_MainWindow):
             self.save_as()
             return
         self._schedule._sfg._graph_id_generator = None
-        with open(self._file_name, "wb") as f:
+        with Path(self._file_name).open("wb") as f:
             pickle.dump(self._schedule, f)
         self._add_recent_file(self._file_name)
         self.update_statusbar(self.tr("Schedule saved successfully"))
@@ -505,7 +506,7 @@ class ScheduleMainWindow(QMainWindow, Ui_MainWindow):
             filename += ".bsc"
         self._file_name = filename
         self._schedule._sfg._graph_id_generator = None
-        with open(self._file_name, "wb") as f:
+        with Path(self._file_name).open("wb") as f:
             pickle.dump(self._schedule, f)
         self._add_recent_file(self._file_name)
         self.update_statusbar(self.tr("Schedule saved successfully"))
@@ -536,7 +537,7 @@ class ScheduleMainWindow(QMainWindow, Ui_MainWindow):
         self._file_name = abs_path_filename
         self._add_recent_file(abs_path_filename)
 
-        with open(self._file_name, "rb") as f:
+        with Path(self._file_name).open("rb") as f:
             schedule = pickle.load(f)
         self.open(schedule)
         settings = QSettings()
@@ -676,7 +677,7 @@ class ScheduleMainWindow(QMainWindow, Ui_MainWindow):
             if not hide_dialog:
                 settings.setValue("scheduler/hide_exit_dialog", checkbox.isChecked())
             self._write_settings()
-            log.info("Exit: %s", os.path.basename(__file__))
+            log.info("Exit: %s", Path(__file__).name)
             if self._ports_accesses_for_storage:
                 self._ports_accesses_for_storage.close()
             if self._execution_time_for_variables:
diff --git a/test/conftest.py b/test/conftest.py
index 0bc4ae71..e296b3c2 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -1,5 +1,5 @@
-import os
 import shutil
+from pathlib import Path
 
 import pytest
 
@@ -14,10 +14,11 @@ from test.fixtures.signal_flow_graph import *
 
 @pytest.fixture
 def datadir(tmpdir, request):
-    filename = request.module.__file__
-    test_dir, _ = os.path.splitext(filename)
+    filepath = Path(request.module.__file__)
 
-    if os.path.isdir(test_dir):
-        shutil.copytree(test_dir, str(tmpdir), dirs_exist_ok=True)
+    test_dir = filepath.parent / filepath.stem
+
+    if test_dir.is_dir():
+        shutil.copytree(test_dir, Path(tmpdir), dirs_exist_ok=True)
 
     return tmpdir
diff --git a/test/unit/test_sfg.py b/test/unit/test_sfg.py
index 8f29cb71..4ba1f3a4 100644
--- a/test/unit/test_sfg.py
+++ b/test/unit/test_sfg.py
@@ -5,7 +5,7 @@ import re
 import string
 import sys
 from collections import Counter
-from os import path, remove
+from pathlib import Path
 
 import numpy as np
 import pytest
@@ -1090,82 +1090,83 @@ class TestRemove:
 
 
 class TestSaveLoadSFG:
-    def get_path(self, existing=False):
-        path_ = "".join(random.choices(string.ascii_uppercase, k=4)) + ".py"
-        while path.exists(path_) if not existing else not path.exists(path_):
-            path_ = "".join(random.choices(string.ascii_uppercase, k=4)) + ".py"
+    # TODO: Rewrite to use TempDir/TempFileF
+    def get_path(self, existing=False) -> Path:
+        path = Path("".join(random.choices(string.ascii_uppercase, k=4)) + ".py")
+        while path.exists() if not existing else not path.exists():
+            path = Path("".join(random.choices(string.ascii_uppercase, k=4)) + ".py")
 
-        return path_
+        return path
 
     def test_save_simple_sfg(self, sfg_simple_filter):
         result = sfg_to_python(sfg_simple_filter)
-        path_ = self.get_path()
+        path = self.get_path()
 
-        assert not path.exists(path_)
-        with open(path_, "w") as file_obj:
+        assert not path.exists()
+        with path.open("w") as file_obj:
             file_obj.write(result)
 
-        assert path.exists(path_)
+        assert path.exists()
 
-        with open(path_) as file_obj:
+        with path.open() as file_obj:
             assert file_obj.read() == result
 
-        remove(path_)
+        path.unlink()
 
     def test_save_complex_sfg(self, precedence_sfg_delays_and_constants):
         result = sfg_to_python(precedence_sfg_delays_and_constants)
-        path_ = self.get_path()
+        path = self.get_path()
 
-        assert not path.exists(path_)
-        with open(path_, "w") as file_obj:
+        assert not path.exists()
+        with path.open("w") as file_obj:
             file_obj.write(result)
 
-        assert path.exists(path_)
+        assert path.exists()
 
-        with open(path_) as file_obj:
+        with path.open() as file_obj:
             assert file_obj.read() == result
 
-        remove(path_)
+        path.unlink()
 
     def test_load_simple_sfg(self, sfg_simple_filter):
         result = sfg_to_python(sfg_simple_filter)
-        path_ = self.get_path()
+        path = self.get_path()
 
-        assert not path.exists(path_)
-        with open(path_, "w") as file_obj:
+        assert not path.exists()
+        with path.open("w") as file_obj:
             file_obj.write(result)
 
-        assert path.exists(path_)
+        assert path.exists()
 
-        simple_filter_, _ = python_to_sfg(path_)
+        simple_filter_, _ = python_to_sfg(path)
 
         assert str(sfg_simple_filter) == str(simple_filter_)
         assert sfg_simple_filter.evaluate([2]) == simple_filter_.evaluate([2])
 
-        remove(path_)
+        path.unlink()
 
     def test_load_complex_sfg(self, precedence_sfg_delays_and_constants):
         result = sfg_to_python(precedence_sfg_delays_and_constants)
-        path_ = self.get_path()
+        path = self.get_path()
 
-        assert not path.exists(path_)
-        with open(path_, "w") as file_obj:
+        assert not path.exists()
+        with path.open("w") as file_obj:
             file_obj.write(result)
 
-        assert path.exists(path_)
+        assert path.exists()
 
-        precedence_sfg_registers_and_constants_, _ = python_to_sfg(path_)
+        precedence_sfg_registers_and_constants_, _ = python_to_sfg(path)
 
         assert str(precedence_sfg_delays_and_constants) == str(
             precedence_sfg_registers_and_constants_
         )
 
-        remove(path_)
+        path.unlink()
 
     def test_load_invalid_path(self):
-        path_ = self.get_path(existing=False)
+        path = self.get_path(existing=False)
         with pytest.raises(FileNotFoundError):
-            python_to_sfg(path_)
+            python_to_sfg(path)
 
 
 class TestGetComponentsOfType:
-- 
GitLab