From d316601e9a72efab49782adabaf4ead3b6689557 Mon Sep 17 00:00:00 2001 From: WorldTeacher Date: Thu, 12 Feb 2026 08:54:19 +0100 Subject: [PATCH] fix: issues (1) --- README.md | 29 ++-- config/config.py | 43 ++++-- docs/themes/tanuki | 1 - pyproject.toml | 30 +--- src/__init__.py | 4 +- src/backend/database.py | 2 +- src/background/documentation_server.py | 2 +- src/core/__init__.py | 2 +- src/core/models.py | 99 +++++++------ src/core/semester.py | 57 +++++--- src/database/connection.py | 20 +-- src/documents/generators.py | 21 +-- src/errors/__init__.py | 2 +- src/parsers/transformers/__init__.py | 8 ++ src/parsers/transformers/transformers.py | 10 +- src/services/webrequest.py | 4 +- src/transformers/__init__.py | 8 ++ src/transformers/transformers.py | 10 +- src/ui/__init__.py | 67 ++++----- src/ui/dialogs/dialog_sources/__init__.py | 3 + src/ui/dialogs/progress.py | 6 + src/ui/userInterface.py | 3 +- src/ui/widgets/admin_query.py | 25 +++- src/utils/documentation.py | 21 +-- src/utils/richtext.py | 21 +-- test.py | 14 +- uv.lock | 163 +++++++++++++++++++--- 27 files changed, 440 insertions(+), 235 deletions(-) delete mode 160000 docs/themes/tanuki diff --git a/README.md b/README.md index fca23a1..f97d21d 100644 --- a/README.md +++ b/README.md @@ -373,26 +373,31 @@ CREATE TABLE IF NOT EXISTS user_preferences ( 2. Convert: `pyside6-uic dialog.ui -o dialog_ui.py` 3. Create dialog class in `src/ui/dialogs/` 4. Connect signals to business logic -### Building Documentation -```bash -# Using uv -uv run mkdocs build -uv run mkdocs serve # View at http://localhost:8000 +## 📚 Documentation -# Or with activated venv -mkdocs build -mkdocs serve -```*[API Documentation](docs/)**: Detailed module documentation -- **[User Manual](docs/index.md)**: Complete user guide (MkDocs) +- **[User Manual](docs/)**: Complete user guide built with Zola and the Tanuki theme +- View documentation at `http://localhost:8000` when running the application ### Building Documentation +The documentation is built using [Zola](https://www.getzola.org/) with the Tanuki theme. + ```bash -mkdocs build -mkdocs serve # View at http://localhost:8000 +# Build documentation using the provided script +.\build_docs.ps1 + +# Or manually: +cd docs +zola build + +# Serve documentation locally for development +cd docs +zola serve # View at http://127.0.0.1:1111 ``` +The built documentation is served automatically when you run the application and access the documentation menu. + ## 🤝 Contributing Contributions are welcome! Please follow these guidelines: diff --git a/config/config.py b/config/config.py index 8db3b6a..fa52ef5 100644 --- a/config/config.py +++ b/config/config.py @@ -1,6 +1,6 @@ from typing import Optional, Any, Union from dataclasses import dataclass -from omegaconf import OmegaConf, DictConfig +from omegaconf import OmegaConf, DictConfig, ListConfig import os from pathlib import Path @@ -140,7 +140,7 @@ class Config: """ - _config: Optional[DictConfig] = None + _config: Optional[Union[DictConfig, ListConfig]] = None config_exists: bool = True def __init__(self, config_path: str): @@ -183,22 +183,25 @@ class Config: """ Reloads the configuration from the file. """ - self._config = OmegaConf.load(self.config_path) + if self.config_path is not None: + self._config = OmegaConf.load(self.config_path) @property def zotero(self): + if self._config is None: + raise RuntimeError("Configuration not loaded") return Zotero(**self._config.zotero) - @property - def zotero_attr(self, name: str): + def get_zotero_attr(self, name: str): return getattr(self.zotero, name) - @zotero_attr.setter - def zotero_attr(self, name: str, value: Any): + def set_zotero_attr(self, name: str, value: Any): self.zotero._setattr(name, value) @property def database(self): + if self._config is None: + raise RuntimeError("Configuration not loaded") return Database(**self._config.database) @property @@ -211,43 +214,57 @@ class Config: @property def openAI(self): + if self._config is None: + raise RuntimeError("Configuration not loaded") return OpenAI(**self._config.openAI) @property def mail(self): + if self._config is None: + raise RuntimeError("Configuration not loaded") return Mail(**self._config.mail) def mail_attr(self, name: str): return getattr(self.mail, name) def set_mail_attr(self, name: str, value: Any): - OmegaConf.update(self._config, f"mail.{name}", value) + if self._config is not None: + OmegaConf.update(self._config, f"mail.{name}", value) def set_database_attr(self, name: str, value: Any): - OmegaConf.update(self._config, f"database.{name}", value) + if self._config is not None: + OmegaConf.update(self._config, f"database.{name}", value) def set_zotero_attr(self, name: str, value: Any): - OmegaConf.update(self._config, f"zotero.{name}", value) + if self._config is not None: + OmegaConf.update(self._config, f"zotero.{name}", value) def set_openai_attr(self, name: str, value: Any): - OmegaConf.update(self._config, f"openAI.{name}", value) + if self._config is not None: + OmegaConf.update(self._config, f"openAI.{name}", value) def set_icon_attr(self, name: str, value: Any): - OmegaConf.update(self._config, f"icons.{name}", value) + if self._config is not None: + OmegaConf.update(self._config, f"icons.{name}", value) @property def save_path(self): + if self._config is None: + raise RuntimeError("Configuration not loaded") return self._config.save_path @save_path.setter def save_path(self, value: str): - self._config.save_path = value + if self._config is not None: + self._config.save_path = value def load_config(self, path, filename): return OmegaConf.load(os.path.join(path, filename)) @property def icons(self): + if self._config is None: + raise RuntimeError("Configuration not loaded") icons = Icons() icons.assign("path", self._config.icon_path) icons.assign("colors", self._config.colors) diff --git a/docs/themes/tanuki b/docs/themes/tanuki deleted file mode 160000 index f81db54..0000000 --- a/docs/themes/tanuki +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f81db54c4edc8270c46375a5540d9c08d677aeac diff --git a/pyproject.toml b/pyproject.toml index 59992f3..b174d55 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,34 +41,14 @@ dev = [ "pytest", "pytest-cov", "pyinstaller>=6.17.0", + "ty>=0.0.15", ] swbtest = ["alive-progress>=3.3.0"] -[tool.bumpversion] -current_version = "1.0.2" -parse = "(?P\\d+)\\.(?P\\d+)\\.(?P\\d+)" -serialize = ["{major}.{minor}.{patch}"] -search = "{current_version}" -replace = "{new_version}" -regex = false -ignore_missing_version = false -ignore_missing_files = false -tag = true -sign_tags = false -tag_name = "v{new_version}" -tag_message = "Bump version: {current_version} → {new_version}" -allow_dirty = true -commit = true -message = "Bump version: {current_version} → {new_version}" -moveable_tags = [] -commit_args = "" -setup_hooks = [] -pre_commit_hooks = [] -post_commit_hooks = [] -[[tool.bumpversion.files]] -filename = "src/__init__.py" -[[tool.bumpversion.files]] -filename = ".version" +[tool.ruff] +line-length = 88 +target-version = "py313" + [[tool.uv.index]] name = "gitea" diff --git a/src/__init__.py b/src/__init__.py index 7172238..c596007 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -33,6 +33,8 @@ if not _user_log_dir: if not _user_config_dir: _user_config_dir = str(get_app_base_path() / "config") +from config import Config # noqa: E402 + LOG_DIR: str = _user_log_dir CONFIG_DIR: str = _user_config_dir @@ -49,8 +51,6 @@ except Exception: Path(LOG_DIR).mkdir(parents=True, exist_ok=True) Path(CONFIG_DIR).mkdir(parents=True, exist_ok=True) -from config import Config - settings = Config(f"{CONFIG_DIR}/config.yaml") DATABASE_DIR: Union[Path, str] = ( # type: ignore diff --git a/src/backend/database.py b/src/backend/database.py index 07db645..2ab1652 100644 --- a/src/backend/database.py +++ b/src/backend/database.py @@ -1216,9 +1216,9 @@ class Database: Optional[int]: the id of the apparat """ - log.debug("Creating apparat: {} - {}", app.appnr, app.name) app = apparat.apparat prof = apparat.prof + log.debug("Creating apparat: {} - {}", app.appnr, app.name) present_prof = self.getProfByName(prof.name()) prof_id = present_prof.id log.debug("Present prof: {}", preview(present_prof, 300)) diff --git a/src/background/documentation_server.py b/src/background/documentation_server.py index 2f7b941..3b8e9ca 100644 --- a/src/background/documentation_server.py +++ b/src/background/documentation_server.py @@ -24,5 +24,5 @@ class DocumentationThread(QThread): self._process.terminate() # terminate the subprocess try: self._process.wait(timeout=5) # wait up to 5 seconds - except: + except Exception: self._process.kill() # force kill if it doesn't stop diff --git a/src/core/__init__.py b/src/core/__init__.py index 15185c3..488ecec 100644 --- a/src/core/__init__.py +++ b/src/core/__init__.py @@ -12,7 +12,7 @@ from .models import ( Subjects, XMLMailSubmission, ) -from .constants import * +from .constants import * # noqa: F403 from .semester import Semester __all__ = [ diff --git a/src/core/models.py b/src/core/models.py index 49ec9b1..b1d09d8 100644 --- a/src/core/models.py +++ b/src/core/models.py @@ -21,7 +21,7 @@ class Prof: telnr: str | None = None # add function that sets the data based on a dict - def from_dict(self, data: dict[str, Union[str, int]]): + def from_dict(self, data: dict[str, Union[str, int]]) -> 'Prof': for key, value in data.items(): if hasattr(self, key): setattr(self, key, value) @@ -38,27 +38,40 @@ class Prof: self._title = value # add function that sets the data from a tuple - def from_tuple(self, data: tuple[Union[str, int], ...]) -> Prof: - self.id = data[0] - self._title = data[1] - self.firstname = data[2] - self.lastname = data[3] - self.fullname = data[4] - self.mail = data[5] - self.telnr = data[6] + def from_tuple(self, data: tuple[Union[int, str, None], ...]) -> 'Prof': + self.id = data[0] if data[0] is not None and isinstance(data[0], int) else None + self._title = str(data[1]) if data[1] is not None else None + self.firstname = str(data[2]) if data[2] is not None else None + self.lastname = str(data[3]) if data[3] is not None else None + self.fullname = str(data[4]) if data[4] is not None else None + self.mail = str(data[5]) if data[5] is not None else None + self.telnr = str(data[6]) if data[6] is not None else None return self def name(self, comma: bool = False) -> Optional[str]: if self.firstname is None and self.lastname is None: - if "," in self.fullname: - self.firstname = self.fullname.split(",")[1].strip() - self.lastname = self.fullname.split(",")[0].strip() + if self.fullname and "," in self.fullname: + parts = self.fullname.split(",") + if len(parts) >= 2: + self.firstname = parts[1].strip() + self.lastname = parts[0].strip() else: return self.fullname if comma: - return f"{self.lastname}, {self.firstname}" - return f"{self.lastname} {self.firstname}" + if self.lastname and self.firstname: + return f"{self.lastname}, {self.firstname}" + elif self.lastname: + return self.lastname + elif self.firstname: + return f", {self.firstname}" + elif self.lastname and self.firstname: + return f"{self.lastname} {self.firstname}" + elif self.lastname: + return self.lastname + elif self.firstname: + return self.firstname + return self.fullname @dataclass @@ -90,10 +103,12 @@ class BookData: if isinstance(self.language, list) and self.language: self.language = [lang.strip() for lang in self.language if lang.strip()] self.language = ",".join(self.language) - self.year = regex.sub(r"[^\d]", "", str(self.year)) if self.year else None + if self.year is not None: + year_str = regex.sub(r"[^\d]", "", str(self.year)) + self.year = int(year_str) if year_str else None self.in_library = True if self.signature else False - def from_dict(self, data: dict) -> BookData: + def from_dict(self, data: dict[str, Any]) -> 'BookData': for key, value in data.items(): setattr(self, key, value) return self @@ -132,23 +147,26 @@ class BookData: del data_dict["old_book"] return json.dumps(data_dict, ensure_ascii=False) - def from_dataclass(self, dataclass: Optional[Any]) -> None: - if dataclass is None: + def from_dataclass(self, data_obj: Optional[Any]) -> None: + if data_obj is None: return - for key, value in dataclass.__dict__.items(): + for key, value in data_obj.__dict__.items(): setattr(self, key, value) def get_book_type(self) -> str: - if "Online" in self.pages: + if self.pages and "Online" in self.pages: return "eBook" return "Druckausgabe" - def from_string(self, data: str) -> BookData: + def from_string(self, data: str) -> 'BookData': ndata = json.loads(data) + # Create a new BookData instance and set its attributes + book_data = BookData() + for key, value in ndata.items(): + setattr(book_data, key, value) + return book_data - return BookData(**ndata) - - def from_LehmannsSearchResult(self, result: Any) -> BookData: + def from_LehmannsSearchResult(self, result: Any) -> 'BookData': self.title = result.title self.author = "; ".join(result.authors) if result.authors else None self.edition = str(result.edition) if result.edition else None @@ -170,7 +188,7 @@ class BookData: def edition_number(self) -> Optional[int]: if self.edition is None: return 0 - match = regex.search(r"(\d+)", self.edition) + match = regex.search(r"(\d+)", self.edition or "") if match: return int(match.group(1)) return 0 @@ -215,13 +233,13 @@ class Subjects(Enum): return self.value[0] @property - def name(self) -> str: + def subject_name(self) -> str: return self.value[1] @classmethod def get_index(cls, name: str) -> Optional[int]: for i in cls: - if i.name == name: + if i.subject_name == name: return i.id - 1 return None @@ -355,25 +373,24 @@ class Book: @dataclass class SemapDocument: - subject: str | None - phoneNumber: int | None - mail: str | None - title: str | None - personName: str | None - personTitle: str | None - title_suggestions: list[str] = None - semester: Union[str, Semester] = None - books: list[Book] = None + subject: str | None = None + phoneNumber: int | None = None + mail: str | None = None + title: str | None = None + personName: str | None = None + personTitle: str | None = None + title_suggestions: list[str] = field(default_factory=list) + semester: Union[str, 'Semester', None] = None + books: list[Book] = field(default_factory=list) eternal: bool = False title_length: int = 0 title_max_length: int = 0 def __post_init__(self) -> None: """.""" - self.title_suggestions = [] - self.phoneNumber = int( - regex.sub(r"[^\d]", "", str(self.phoneNumber)), - ) + if self.phoneNumber is not None: + phone_str = regex.sub(r"[^\d]", "", str(self.phoneNumber)) + self.phoneNumber = int(phone_str) if phone_str else None @property def nameSetter(self): @@ -399,7 +416,7 @@ class SemapDocument: def renameSemester(self) -> None: from src.services.openai import semester_converter - if self.semester: + if self.semester and isinstance(self.semester, str): if ", Dauer" in self.semester: self.semester = self.semester.split(",")[0] self.eternal = True diff --git a/src/core/semester.py b/src/core/semester.py index 1d149a4..cef07db 100644 --- a/src/core/semester.py +++ b/src/core/semester.py @@ -1,4 +1,4 @@ -"""Semester helper class +"""Semester helper class. A small utility around the *German* academic calendar that distinguishes between *Wintersemester* (WiSe) and *Sommersemester* (SoSe). @@ -7,7 +7,7 @@ Key points ---------- * A **`Semester`** is identified by a *term* ("SoSe" or "WiSe") and the last two digits of the calendar year in which the term *starts*. -* Formatting **never** pads the year with a leading zero – so ``6`` stays ``6``. +* Formatting **never** pads the year with a leading zero - so ``6`` stays ``6``. * ``offset(n)`` and the static ``generate_missing`` reliably walk the timeline one semester at a time with correct year transitions: @@ -26,13 +26,13 @@ class Semester: """Represents a German university semester (WiSe or SoSe).""" # ------------------------------------------------------------------ - # Class‑level defaults – will be *copied* to each instance and then + # Class-level defaults - will be *copied* to each instance and then # potentially overwritten in ``__init__``. # ------------------------------------------------------------------ _year: int | None = None # Will be set in __post_init__ - _semester: str | None = None # "WiSe" or "SoSe" – set later + _semester: str | None = None # "WiSe" or "SoSe" - set later _month: int | None = None # Will be set in __post_init__ - value: str | None = None # Human‑readable label, e.g. "WiSe 23/24" + value: str | None = None # Human-readable label, e.g. "WiSe 23/24" # ------------------------------------------------------------------ # Construction helpers @@ -89,17 +89,22 @@ class Semester: # ------------------------------------------------------------------ def _generate_semester_from_month(self) -> None: """Infer *WiSe* / *SoSe* from the month attribute.""" - self._semester = "WiSe" if (self._month <= 3 or self._month > 9) else "SoSe" + if self._month is not None: + self._semester = "WiSe" if (self._month <= 3 or self._month > 9) else "SoSe" + else: + self._semester = "WiSe" # Default value if month is None def _compute_value(self) -> None: - """Human‑readable semester label – e.g. ``WiSe 23/24`` or ``SoSe 24``.""" - year = self._year - if self._semester == "WiSe": - next_year = (year + 1) % 100 # wrap 99 → 0 - - self.value = f"WiSe {year}/{next_year}" - else: # SoSe - self.value = f"SoSe {year}" + """Human-readable semester label - e.g. ``WiSe 23/24`` or ``SoSe 24``.""" + if self._year is not None: + year = self._year + if self._semester == "WiSe": + next_year = (year + 1) % 100 # wrap 99 → 0 + self.value = f"WiSe {year}/{next_year}" + else: # SoSe + self.value = f"SoSe {year}" + else: + self.value = "" # ------------------------------------------------------------------ # Public API @@ -117,10 +122,12 @@ class Semester: if value == 0: return Semester(self._year, self._semester) + if self._year is None: + raise ValueError("Cannot offset from a semester with no year") current_idx = self._year * 2 + (0 if self._semester == "SoSe" else 1) target_idx = current_idx + value if target_idx < 0: - raise ValueError("offset would result in a negative year – not supported") + raise ValueError("offset would result in a negative year - not supported") new_year, semester_bit = divmod(target_idx, 2) new_semester = "SoSe" if semester_bit == 0 else "WiSe" @@ -164,10 +171,14 @@ class Semester: @property def year(self) -> int: + if self._year is None: + raise ValueError("Year is not set for this semester") return self._year @property def semester(self) -> str: + if self._semester is None: + raise ValueError("Semester is not set for this semester") return self._semester # ------------------------------------------------------------------ @@ -178,14 +189,14 @@ class Semester: """Return all consecutive semesters from *start* to *end* (inclusive).""" if not isinstance(start, Semester) or not isinstance(end, Semester): raise TypeError("start and end must be Semester instances") - if start.is_future_semester(end) and not start.isMatch(end): + if start.is_future_semester(end) and not start.is_match(end): raise ValueError("'start' must not be after 'end'") - chain: list[Semester] = [start.value] + chain: list[str] = [str(start)] current = start - while not current.isMatch(end): + while not current.is_match(end): current = current.next - chain.append(current.value) + chain.append(str(current)) if len(chain) > 1000: # sanity guard raise RuntimeError("generate_missing exceeded sane iteration limit") return chain @@ -195,9 +206,9 @@ class Semester: # ------------------------------------------------------------------ @classmethod def from_string(cls, s: str) -> Semester: - """Parse a human‑readable semester label and return a :class:`Semester`. + """Parse a human-readable semester label and return a :class:`Semester`. - Accepted formats (case‑insensitive):: + Accepted formats (case-insensitive):: "SoSe " → SoSe of year YY "WiSe /" → Winter term starting in YY @@ -212,7 +223,7 @@ class Semester: m = re.fullmatch(pattern, s, flags=re.IGNORECASE) if not m: raise ValueError( - "invalid semester string format – expected 'SoSe YY' or 'WiSe YY/YY' (spacing flexible)", + "invalid semester string format - expected 'SoSe YY' or 'WiSe YY/YY' (spacing flexible)", ) term_raw, y1_str, y2_str = m.groups() @@ -236,7 +247,7 @@ class Semester: return cls(year, "WiSe") -# ------------------------- quick self‑test ------------------------- +# ------------------------- quick self-test ------------------------- if __name__ == "__main__": # Chain generation demo ------------------------------------------------ s_start = Semester(6, "SoSe") # SoSe 6 diff --git a/src/database/connection.py b/src/database/connection.py index cd77c83..eabac9c 100644 --- a/src/database/connection.py +++ b/src/database/connection.py @@ -123,7 +123,7 @@ class Database: try: if self.db_path is not None: self.run_migrations() - except Exception as e: + except (sql.Error, OSError, IOError) as e: log.error(f"Error while running migrations: {e}") # --- Migration helpers integrated into Database --- @@ -212,9 +212,9 @@ class Database: ).__str__() return result[0] - def getElsaMediaType(self, id): + def getElsaMediaType(self, media_id): query = "SELECT type FROM elsa_media WHERE id=?" - return self.query_db(query, (id,), one=True)[0] + return self.query_db(query, (media_id,), one=True)[0] def get_db_contents(self) -> Union[List[Tuple[Any]], None]: """ @@ -736,7 +736,7 @@ class Database: try: bloat.debug("Recreated file blob size: {} bytes", len(blob)) bloat.debug("Recreated file blob (preview): {}", preview(blob, 2000)) - except Exception: + except (TypeError, UnicodeDecodeError, ValueError): bloat.debug("Recreated file blob (preview): {}", preview(blob, 2000)) tempdir = settings.database.temp.expanduser() if not tempdir.exists(): @@ -990,16 +990,16 @@ class Database: person = Prof() return person.from_tuple(data) - def getProf(self, id) -> Prof: + def getProf(self, prof_id) -> Prof: """Get a professor based on the id Args: - id ([type]): the id of the professor + prof_id ([type]): the id of the professor Returns: Prof: a Prof object containing the data of the professor """ - data = self.query_db("SELECT * FROM prof WHERE id=?", (id,), one=True) + data = self.query_db("SELECT * FROM prof WHERE id=?", (prof_id,), one=True) return Prof().from_tuple(data) def getProfs(self) -> list[Prof]: @@ -1278,17 +1278,17 @@ class Database: # print(apparat_nr, app_id) self.query_db("UPDATE media SET deleted=1 WHERE app_id=?", (app_id,)) - def isEternal(self, id): + def isEternal(self, apparat_id): """check if the apparat is eternal (dauerapparat) Args: - id (int): the id of the apparat to be checked + apparat_id (int): the id of the apparat to be checked Returns: int: the state of the apparat """ return self.query_db( - "SELECT dauer FROM semesterapparat WHERE appnr=?", (id,), one=True + "SELECT dauer FROM semesterapparat WHERE appnr=?", (apparat_id,), one=True ) def getApparatName(self, app_id: Union[str, int], prof_id: Union[str, int]): diff --git a/src/documents/generators.py b/src/documents/generators.py index bd163cd..cc3ccb6 100644 --- a/src/documents/generators.py +++ b/src/documents/generators.py @@ -1,6 +1,7 @@ import os from datetime import datetime from os.path import basename +from pathlib import Path from docx import Document from docx.enum.text import WD_PARAGRAPH_ALIGNMENT @@ -69,20 +70,20 @@ class SemesterDocument: full: bool = False, ): assert isinstance(apparats, list), SemesterError( - "Apparats must be a list of tuples" + "Apparats must be a list of tuples", ) assert all(isinstance(apparat, tuple) for apparat in apparats), SemesterError( - "Apparats must be a list of tuples" + "Apparats must be a list of tuples", ) assert all(isinstance(apparat[0], int) for apparat in apparats), SemesterError( - "Apparat numbers must be integers" + "Apparat numbers must be integers", ) assert all(isinstance(apparat[1], str) for apparat in apparats), SemesterError( - "Apparat names must be strings" + "Apparat names must be strings", ) assert isinstance(semester, str), SemesterError("Semester must be a string") assert "." not in filename and isinstance(filename, str), SemesterError( - "Filename must be a string and not contain an extension" + "Filename must be a string and not contain an extension", ) self.doc = Document() self.apparats = apparats @@ -108,8 +109,7 @@ class SemesterDocument: log.info("Document printed") def set_table_border(self, table): - """ - Adds a full border to the table. + """Adds a full border to the table. :param table: Table object to which the border will be applied. """ @@ -150,7 +150,8 @@ class SemesterDocument: trPr = row._tr.get_or_add_trPr() # Get or add the element trHeight = OxmlElement("w:trHeight") trHeight.set( - qn("w:val"), str(int(Pt(15).pt * 20)) + qn("w:val"), + str(int(Pt(15).pt * 20)), ) # Convert points to twips trHeight.set(qn("w:hRule"), "exact") # Use "exact" for fixed height trPr.append(trHeight) @@ -233,7 +234,7 @@ class SemesterDocument: self.save_document(self.filename + ".docx") docpath = os.path.abspath(self.filename + ".docx") doc = word.Documents.Open(docpath) - curdir = os.getcwd() + curdir = Path.cwd() doc.SaveAs(f"{curdir}/{self.filename}.pdf", FileFormat=17) doc.Close() word.Quit() @@ -317,7 +318,7 @@ class SemapSchilder: self.save_document() docpath = os.path.abspath(f"{self.filename}.docx") doc = word.Documents.Open(docpath) - curdir = os.getcwd() + curdir = Path.cwd() doc.SaveAs(f"{curdir}/{self.filename}.pdf", FileFormat=17) doc.Close() word.Quit() diff --git a/src/errors/__init__.py b/src/errors/__init__.py index 0a24916..96c18e2 100644 --- a/src/errors/__init__.py +++ b/src/errors/__init__.py @@ -1,2 +1,2 @@ # import basic error classes -from .DatabaseErrors import * +from .DatabaseErrors import * # noqa: F403 diff --git a/src/parsers/transformers/__init__.py b/src/parsers/transformers/__init__.py index 96f8070..6b2bf0d 100644 --- a/src/parsers/transformers/__init__.py +++ b/src/parsers/transformers/__init__.py @@ -6,3 +6,11 @@ from .transformers import ( RDSData, RISData, ) + +# Explicit re-exports to avoid F401 warnings +RDS_AVAIL_DATA = RDS_AVAIL_DATA +ARRAYData = ARRAYData +BibTeXData = BibTeXData +COinSData = COinSData +RDSData = RDSData +RISData = RISData diff --git a/src/parsers/transformers/transformers.py b/src/parsers/transformers/transformers.py index 2099f91..0318640 100644 --- a/src/parsers/transformers/transformers.py +++ b/src/parsers/transformers/transformers.py @@ -141,7 +141,7 @@ class ARRAYData: source = source.replace("\t", "").replace("\r", "") source = source.split(search)[1].split(")")[0] return _get_line(source, entry).replace("=>", "").strip() - except: + except Exception: return "" def _get_isbn(source: str) -> list: @@ -157,7 +157,7 @@ class ARRAYData: continue ret.append(isb) if isb not in ret else None return ret - except: + except Exception: isbn = [] return isbn @@ -294,7 +294,7 @@ class COinSData: try: data = source.split(f"{search}=")[1] # .split("")[0].strip() return data.split("rft")[0].strip() if "rft" in data else data - except: + except Exception: return "" return BookData( @@ -319,7 +319,7 @@ class RISData: try: data = source.split(f"{search} - ")[1] # .split("")[0].strip() return data.split("\n")[0].strip() if "\n" in data else data - except: + except Exception: return "" return BookData( @@ -356,7 +356,7 @@ class BibTeXData: .replace("[", "") .replace("];", "") ) - except: + except Exception: return "" return BookData( diff --git a/src/services/webrequest.py b/src/services/webrequest.py index b7c0709..8d9f5e4 100644 --- a/src/services/webrequest.py +++ b/src/services/webrequest.py @@ -9,11 +9,11 @@ from ratelimit import limits, sleep_and_retry from src.core.models import BookData from src.shared.logging import log, get_bloat_logger, preview +from src.transformers import ARRAYData, BibTeXData, COinSData, RDSData, RISData +from src.transformers.transformers import RDS_AVAIL_DATA, RDS_GENERIC_DATA # bloat logger for large/raw HTTP responses bloat = get_bloat_logger() -from src.transformers import ARRAYData, BibTeXData, COinSData, RDSData, RISData -from src.transformers.transformers import RDS_AVAIL_DATA, RDS_GENERIC_DATA # logger.add(sys.stderr, format="{time} {level} {message}", level="INFO") diff --git a/src/transformers/__init__.py b/src/transformers/__init__.py index 96f8070..6b2bf0d 100644 --- a/src/transformers/__init__.py +++ b/src/transformers/__init__.py @@ -6,3 +6,11 @@ from .transformers import ( RDSData, RISData, ) + +# Explicit re-exports to avoid F401 warnings +RDS_AVAIL_DATA = RDS_AVAIL_DATA +ARRAYData = ARRAYData +BibTeXData = BibTeXData +COinSData = COinSData +RDSData = RDSData +RISData = RISData diff --git a/src/transformers/transformers.py b/src/transformers/transformers.py index 1a40c83..8615f24 100644 --- a/src/transformers/transformers.py +++ b/src/transformers/transformers.py @@ -141,7 +141,7 @@ class ARRAYData: source = source.replace("\t", "").replace("\r", "") source = source.split(search)[1].split(")")[0] return _get_line(source, entry).replace("=>", "").strip() - except: + except Exception: return "" def _get_isbn(source: str) -> list: @@ -157,7 +157,7 @@ class ARRAYData: continue ret.append(isb) if isb not in ret else None return ret - except: + except Exception: isbn = [] return isbn @@ -294,7 +294,7 @@ class COinSData: try: data = source.split(f"{search}=")[1] # .split("")[0].strip() return data.split("rft")[0].strip() if "rft" in data else data - except: + except Exception: return "" return BookData( @@ -319,7 +319,7 @@ class RISData: try: data = source.split(f"{search} - ")[1] # .split("")[0].strip() return data.split("\n")[0].strip() if "\n" in data else data - except: + except Exception: return "" return BookData( @@ -356,7 +356,7 @@ class BibTeXData: .replace("[", "") .replace("];", "") ) - except: + except Exception: return "" return BookData( diff --git a/src/ui/__init__.py b/src/ui/__init__.py index 0867516..8a99435 100644 --- a/src/ui/__init__.py +++ b/src/ui/__init__.py @@ -1,32 +1,35 @@ -import pathlib - -from .semesterapparat_ui_ui import Ui_MainWindow as Ui_Semesterapparat - -# from .dialogs import ( -# ApparatExtendDialog, -# Mail_Dialog, -# Settings, -# edit_bookdata_ui, -# login_ui, -# medienadder_ui, -# parsed_titles_ui, -# popus_confirm, -# reminder_ui, -# About, -# ElsaAddEntry, -# ) -# from .widgets import ( -# FilePicker, -# StatusWidget, -# CalendarEntry, -# MessageCalendar, -# SearchStatisticPage, # -# DataGraph, -# ElsaDialog, -# UserCreate, -# EditUser, -# EditProf, -# ) -path = pathlib.Path(__file__).parent.absolute() -# from .mainwindow import Ui_MainWindow as Ui_MainWindow -# from .sap import Ui_MainWindow as MainWindow_SAP +import pathlib + +from .semesterapparat_ui_ui import Ui_MainWindow as Ui_Semesterapparat + +# Explicit re-export to avoid F401 warnings +Ui_Semesterapparat = Ui_Semesterapparat + +# from .dialogs import ( +# ApparatExtendDialog, +# Mail_Dialog, +# Settings, +# edit_bookdata_ui, +# login_ui, +# medienadder_ui, +# parsed_titles_ui, +# popus_confirm, +# reminder_ui, +# About, +# ElsaAddEntry, +# ) +# from .widgets import ( +# FilePicker, +# StatusWidget, +# CalendarEntry, +# MessageCalendar, +# SearchStatisticPage, # +# DataGraph, +# ElsaDialog, +# UserCreate, +# EditUser, +# EditProf, +# ) +path = pathlib.Path(__file__).parent.absolute() +# from .mainwindow import Ui_MainWindow as Ui_MainWindow +# from .sap import Ui_MainWindow as MainWindow_SAP diff --git a/src/ui/dialogs/dialog_sources/__init__.py b/src/ui/dialogs/dialog_sources/__init__.py index 345e7a2..7c17c6d 100644 --- a/src/ui/dialogs/dialog_sources/__init__.py +++ b/src/ui/dialogs/dialog_sources/__init__.py @@ -1 +1,4 @@ from .newMailTemplateDesigner_ui import Ui_Dialog as NewMailTemplateDesignerDialog + +# Explicit re-export to avoid F401 warnings +NewMailTemplateDesignerDialog = NewMailTemplateDesignerDialog diff --git a/src/ui/dialogs/progress.py b/src/ui/dialogs/progress.py index 030f2ca..e7873e1 100644 --- a/src/ui/dialogs/progress.py +++ b/src/ui/dialogs/progress.py @@ -9,6 +9,12 @@ from src.services.lehmanns import LehmannsClient from src.services.sru import SWB +def filter_prefer_swb(response): + """Filter function to prefer SWB results when available.""" + # This is a placeholder implementation - adjust based on actual requirements + return response + + class CheckThread(QtCore.QThread): updateSignal = QtCore.Signal() total_entries_signal = QtCore.Signal(int) diff --git a/src/ui/userInterface.py b/src/ui/userInterface.py index 151a402..8246960 100644 --- a/src/ui/userInterface.py +++ b/src/ui/userInterface.py @@ -180,7 +180,8 @@ class Ui(QtWidgets.QMainWindow, Ui_Semesterapparat): # self.update_app_media_list() self.populate_prof_dropdown() self.populate_appfach_dropdown() - # if the focus is changed from the prof name dropdown, set the prof data if the prof exists in the database, otherwise show a message + # if the focus is changed from the prof name dropdown, set the prof data if the + # prof exists in the database, otherwise show a message self.drpdwn_prof_name.currentIndexChanged.connect(self.set_prof_data) # type:ignore self.cancel_active_selection.clicked.connect(self.btn_cancel_active_selection) # type:ignore self.check_eternal_app.stateChanged.connect(self.set_state) # type:ignore diff --git a/src/ui/widgets/admin_query.py b/src/ui/widgets/admin_query.py index f1f860e..bcac232 100644 --- a/src/ui/widgets/admin_query.py +++ b/src/ui/widgets/admin_query.py @@ -4,15 +4,36 @@ from src.database import Database from src.utils.icon import Icon -class AdminQueryWidget(QtWidgets.QWidget, Ui_Form): +class AdminQueryWidget(QtWidgets.QWidget): def __init__(self, parent=None): super().__init__(parent) - self.setupUi(self) + self.setupUi() self.setWindowIcon(Icon("db_search").icon) self.db = Database() # Connect the button click to the method self.sendquery.clicked.connect(self.on_pushButton_clicked) + def setupUi(self): + # Create the layout and widgets + layout = QtWidgets.QVBoxLayout(self) + + # Create SQL query input area + self.sqlquery = QtWidgets.QTextEdit() + self.sqlquery.setPlaceholderText("Enter SQL query here...") + + # Create execute button + self.sendquery = QtWidgets.QPushButton("Execute Query") + + # Create results table + self.queryResult = QtWidgets.QTableWidget() + self.queryResult.setColumnCount(5) # Adjust as needed + self.queryResult.setHorizontalHeaderLabels(["Column 1", "Column 2", "Column 3", "Column 4", "Column 5"]) + + # Add widgets to layout + layout.addWidget(self.sqlquery) + layout.addWidget(self.sendquery) + layout.addWidget(self.queryResult) + def on_pushButton_clicked(self): # Handle button click event self.queryResult.setRowCount(0) # Clear previous results diff --git a/src/utils/documentation.py b/src/utils/documentation.py index 477d78c..6542c40 100644 --- a/src/utils/documentation.py +++ b/src/utils/documentation.py @@ -1,11 +1,13 @@ +"""Utilities for managing documentation server functionality.""" + import logging -import os import subprocess import sys +from pathlib import Path from src import LOG_DIR -log_path = os.path.join(LOG_DIR, "web_documentation.log") +log_path = Path(LOG_DIR) / "web_documentation.log" # Replace the default StreamHandler with a FileHandler logging.basicConfig( @@ -19,13 +21,12 @@ logger = logging.getLogger(__name__) # inherits the same file handler docport = 8000 - def start_documentation_server(): - """ - Start the Zensical documentation server as a subprocess. - + """Start the Zensical documentation server as a subprocess. + Returns: subprocess.Popen: The subprocess object, or None if startup failed. + """ try: # Prepare subprocess arguments @@ -33,7 +34,7 @@ def start_documentation_server(): if sys.platform == "win32": # Hide console window on Windows creationflags = subprocess.CREATE_NO_WINDOW - + # Start subprocess with all output suppressed process = subprocess.Popen( ["uv", "run", "zensical", "serve"], @@ -41,12 +42,12 @@ def start_documentation_server(): stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL, creationflags=creationflags, - cwd=os.getcwd(), + cwd=Path.cwd(), ) - + logger.info(f"Documentation server started with PID {process.pid}") return process - + except Exception as e: logger.error(f"Failed to start documentation server: {e}") return None diff --git a/src/utils/richtext.py b/src/utils/richtext.py index bd163cd..cc3ccb6 100644 --- a/src/utils/richtext.py +++ b/src/utils/richtext.py @@ -1,6 +1,7 @@ import os from datetime import datetime from os.path import basename +from pathlib import Path from docx import Document from docx.enum.text import WD_PARAGRAPH_ALIGNMENT @@ -69,20 +70,20 @@ class SemesterDocument: full: bool = False, ): assert isinstance(apparats, list), SemesterError( - "Apparats must be a list of tuples" + "Apparats must be a list of tuples", ) assert all(isinstance(apparat, tuple) for apparat in apparats), SemesterError( - "Apparats must be a list of tuples" + "Apparats must be a list of tuples", ) assert all(isinstance(apparat[0], int) for apparat in apparats), SemesterError( - "Apparat numbers must be integers" + "Apparat numbers must be integers", ) assert all(isinstance(apparat[1], str) for apparat in apparats), SemesterError( - "Apparat names must be strings" + "Apparat names must be strings", ) assert isinstance(semester, str), SemesterError("Semester must be a string") assert "." not in filename and isinstance(filename, str), SemesterError( - "Filename must be a string and not contain an extension" + "Filename must be a string and not contain an extension", ) self.doc = Document() self.apparats = apparats @@ -108,8 +109,7 @@ class SemesterDocument: log.info("Document printed") def set_table_border(self, table): - """ - Adds a full border to the table. + """Adds a full border to the table. :param table: Table object to which the border will be applied. """ @@ -150,7 +150,8 @@ class SemesterDocument: trPr = row._tr.get_or_add_trPr() # Get or add the element trHeight = OxmlElement("w:trHeight") trHeight.set( - qn("w:val"), str(int(Pt(15).pt * 20)) + qn("w:val"), + str(int(Pt(15).pt * 20)), ) # Convert points to twips trHeight.set(qn("w:hRule"), "exact") # Use "exact" for fixed height trPr.append(trHeight) @@ -233,7 +234,7 @@ class SemesterDocument: self.save_document(self.filename + ".docx") docpath = os.path.abspath(self.filename + ".docx") doc = word.Documents.Open(docpath) - curdir = os.getcwd() + curdir = Path.cwd() doc.SaveAs(f"{curdir}/{self.filename}.pdf", FileFormat=17) doc.Close() word.Quit() @@ -317,7 +318,7 @@ class SemapSchilder: self.save_document() docpath = os.path.abspath(f"{self.filename}.docx") doc = word.Documents.Open(docpath) - curdir = os.getcwd() + curdir = Path.cwd() doc.SaveAs(f"{curdir}/{self.filename}.pdf", FileFormat=17) doc.Close() word.Quit() diff --git a/test.py b/test.py index e9e5230..0a8a523 100644 --- a/test.py +++ b/test.py @@ -1,10 +1,12 @@ -from src.services.webrequest import BibTextTransformer, TransformerType, WebRequest +# from src.services.webrequest import BibTextTransformer, TransformerType, WebRequest -transformer = BibTextTransformer(TransformerType.RDS) +# transformer = BibTextTransformer(TransformerType.RDS) -data = WebRequest().set_apparat(71) -data = data.get_ppn("CU 3700 R244 (2)").get_data() +# data = WebRequest().set_apparat(71) +# data = data.get_ppn("CU 3700 R244 (2)").get_data() -rds = transformer.get_data(data).return_data("rds_availability") +# rds = transformer.get_data(data).return_data("rds_availability") + +# print(rds) +from src.parsers.xml_parser import eml_parser, eml_to_semap -print(rds) diff --git a/uv.lock b/uv.lock index 439d8d4..03b829a 100644 --- a/uv.lock +++ b/uv.lock @@ -307,6 +307,75 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/03/2f/ca9029d5da14b5a3a103d6061149a4a94a54ab848f56c7d2809dbb36f48c/comtypes-1.4.15-py3-none-any.whl", hash = "sha256:cda90486de8762ec57d7ce04e68721920911f3f03415cb29afdf7609c427c7e3", size = 274650, upload-time = "2026-01-19T23:45:44.34Z" }, ] +[[package]] +name = "coverage" +version = "7.13.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb56630faa728da46a27b8c6eb46f9d557c68fff12b65/coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91", size = 827239, upload-time = "2026-02-09T12:59:03.86Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/23/aad45061a31677d68e47499197a131eea55da4875d16c1f42021ab963503/coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b66a2da594b6068b48b2692f043f35d4d3693fb639d5ea8b39533c2ad9ac3ab9", size = 219474, upload-time = "2026-02-09T12:57:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/a5/70/9b8b67a0945f3dfec1fd896c5cefb7c19d5a3a6d74630b99a895170999ae/coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3599eb3992d814d23b35c536c28df1a882caa950f8f507cef23d1cbf334995ac", size = 219844, upload-time = "2026-02-09T12:57:20.66Z" }, + { url = "https://files.pythonhosted.org/packages/97/fd/7e859f8fab324cef6c4ad7cff156ca7c489fef9179d5749b0c8d321281c2/coverage-7.13.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93550784d9281e374fb5a12bf1324cc8a963fd63b2d2f223503ef0fd4aa339ea", size = 250832, upload-time = "2026-02-09T12:57:22.007Z" }, + { url = "https://files.pythonhosted.org/packages/e4/dc/b2442d10020c2f52617828862d8b6ee337859cd8f3a1f13d607dddda9cf7/coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b720ce6a88a2755f7c697c23268ddc47a571b88052e6b155224347389fdf6a3b", size = 253434, upload-time = "2026-02-09T12:57:23.339Z" }, + { url = "https://files.pythonhosted.org/packages/5a/88/6728a7ad17428b18d836540630487231f5470fb82454871149502f5e5aa2/coverage-7.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b322db1284a2ed3aa28ffd8ebe3db91c929b7a333c0820abec3d838ef5b3525", size = 254676, upload-time = "2026-02-09T12:57:24.774Z" }, + { url = "https://files.pythonhosted.org/packages/7c/bc/21244b1b8cedf0dff0a2b53b208015fe798d5f2a8d5348dbfece04224fff/coverage-7.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4594c67d8a7c89cf922d9df0438c7c7bb022ad506eddb0fdb2863359ff78242", size = 256807, upload-time = "2026-02-09T12:57:26.125Z" }, + { url = "https://files.pythonhosted.org/packages/97/a0/ddba7ed3251cff51006737a727d84e05b61517d1784a9988a846ba508877/coverage-7.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:53d133df809c743eb8bce33b24bcababb371f4441340578cd406e084d94a6148", size = 251058, upload-time = "2026-02-09T12:57:27.614Z" }, + { url = "https://files.pythonhosted.org/packages/9b/55/e289addf7ff54d3a540526f33751951bf0878f3809b47f6dfb3def69c6f7/coverage-7.13.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76451d1978b95ba6507a039090ba076105c87cc76fc3efd5d35d72093964d49a", size = 252805, upload-time = "2026-02-09T12:57:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/13/4e/cc276b1fa4a59be56d96f1dabddbdc30f4ba22e3b1cd42504c37b3313255/coverage-7.13.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7f57b33491e281e962021de110b451ab8a24182589be17e12a22c79047935e23", size = 250766, upload-time = "2026-02-09T12:57:30.522Z" }, + { url = "https://files.pythonhosted.org/packages/94/44/1093b8f93018f8b41a8cf29636c9292502f05e4a113d4d107d14a3acd044/coverage-7.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1731dc33dc276dafc410a885cbf5992f1ff171393e48a21453b78727d090de80", size = 254923, upload-time = "2026-02-09T12:57:31.946Z" }, + { url = "https://files.pythonhosted.org/packages/8b/55/ea2796da2d42257f37dbea1aab239ba9263b31bd91d5527cdd6db5efe174/coverage-7.13.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:bd60d4fe2f6fa7dff9223ca1bbc9f05d2b6697bc5961072e5d3b952d46e1b1ea", size = 250591, upload-time = "2026-02-09T12:57:33.842Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fa/7c4bb72aacf8af5020675aa633e59c1fbe296d22aed191b6a5b711eb2bc7/coverage-7.13.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9181a3ccead280b828fae232df12b16652702b49d41e99d657f46cc7b1f6ec7a", size = 252364, upload-time = "2026-02-09T12:57:35.743Z" }, + { url = "https://files.pythonhosted.org/packages/5c/38/a8d2ec0146479c20bbaa7181b5b455a0c41101eed57f10dd19a78ab44c80/coverage-7.13.4-cp313-cp313-win32.whl", hash = "sha256:f53d492307962561ac7de4cd1de3e363589b000ab69617c6156a16ba7237998d", size = 222010, upload-time = "2026-02-09T12:57:37.25Z" }, + { url = "https://files.pythonhosted.org/packages/e2/0c/dbfafbe90a185943dcfbc766fe0e1909f658811492d79b741523a414a6cc/coverage-7.13.4-cp313-cp313-win_amd64.whl", hash = "sha256:e6f70dec1cc557e52df5306d051ef56003f74d56e9c4dd7ddb07e07ef32a84dd", size = 222818, upload-time = "2026-02-09T12:57:38.734Z" }, + { url = "https://files.pythonhosted.org/packages/04/d1/934918a138c932c90d78301f45f677fb05c39a3112b96fd2c8e60503cdc7/coverage-7.13.4-cp313-cp313-win_arm64.whl", hash = "sha256:fb07dc5da7e849e2ad31a5d74e9bece81f30ecf5a42909d0a695f8bd1874d6af", size = 221438, upload-time = "2026-02-09T12:57:40.223Z" }, + { url = "https://files.pythonhosted.org/packages/52/57/ee93ced533bcb3e6df961c0c6e42da2fc6addae53fb95b94a89b1e33ebd7/coverage-7.13.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40d74da8e6c4b9ac18b15331c4b5ebc35a17069410cad462ad4f40dcd2d50c0d", size = 220165, upload-time = "2026-02-09T12:57:41.639Z" }, + { url = "https://files.pythonhosted.org/packages/c5/e0/969fc285a6fbdda49d91af278488d904dcd7651b2693872f0ff94e40e84a/coverage-7.13.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4223b4230a376138939a9173f1bdd6521994f2aff8047fae100d6d94d50c5a12", size = 220516, upload-time = "2026-02-09T12:57:44.215Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b8/9531944e16267e2735a30a9641ff49671f07e8138ecf1ca13db9fd2560c7/coverage-7.13.4-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1d4be36a5114c499f9f1f9195e95ebf979460dbe2d88e6816ea202010ba1c34b", size = 261804, upload-time = "2026-02-09T12:57:45.989Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f3/e63df6d500314a2a60390d1989240d5f27318a7a68fa30ad3806e2a9323e/coverage-7.13.4-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:200dea7d1e8095cc6e98cdabe3fd1d21ab17d3cee6dab00cadbb2fe35d9c15b9", size = 263885, upload-time = "2026-02-09T12:57:47.42Z" }, + { url = "https://files.pythonhosted.org/packages/f3/67/7654810de580e14b37670b60a09c599fa348e48312db5b216d730857ffe6/coverage-7.13.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8eb931ee8e6d8243e253e5ed7336deea6904369d2fd8ae6e43f68abbf167092", size = 266308, upload-time = "2026-02-09T12:57:49.345Z" }, + { url = "https://files.pythonhosted.org/packages/37/6f/39d41eca0eab3cc82115953ad41c4e77935286c930e8fad15eaed1389d83/coverage-7.13.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:75eab1ebe4f2f64d9509b984f9314d4aa788540368218b858dad56dc8f3e5eb9", size = 267452, upload-time = "2026-02-09T12:57:50.811Z" }, + { url = "https://files.pythonhosted.org/packages/50/6d/39c0fbb8fc5cd4d2090811e553c2108cf5112e882f82505ee7495349a6bf/coverage-7.13.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c35eb28c1d085eb7d8c9b3296567a1bebe03ce72962e932431b9a61f28facf26", size = 261057, upload-time = "2026-02-09T12:57:52.447Z" }, + { url = "https://files.pythonhosted.org/packages/a4/a2/60010c669df5fa603bb5a97fb75407e191a846510da70ac657eb696b7fce/coverage-7.13.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb88b316ec33760714a4720feb2816a3a59180fd58c1985012054fa7aebee4c2", size = 263875, upload-time = "2026-02-09T12:57:53.938Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d9/63b22a6bdbd17f1f96e9ed58604c2a6b0e72a9133e37d663bef185877cf6/coverage-7.13.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7d41eead3cc673cbd38a4417deb7fd0b4ca26954ff7dc6078e33f6ff97bed940", size = 261500, upload-time = "2026-02-09T12:57:56.012Z" }, + { url = "https://files.pythonhosted.org/packages/70/bf/69f86ba1ad85bc3ad240e4c0e57a2e620fbc0e1645a47b5c62f0e941ad7f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:fb26a934946a6afe0e326aebe0730cdff393a8bc0bbb65a2f41e30feddca399c", size = 265212, upload-time = "2026-02-09T12:57:57.5Z" }, + { url = "https://files.pythonhosted.org/packages/ae/f2/5f65a278a8c2148731831574c73e42f57204243d33bedaaf18fa79c5958f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:dae88bc0fc77edaa65c14be099bd57ee140cf507e6bfdeea7938457ab387efb0", size = 260398, upload-time = "2026-02-09T12:57:59.027Z" }, + { url = "https://files.pythonhosted.org/packages/ef/80/6e8280a350ee9fea92f14b8357448a242dcaa243cb2c72ab0ca591f66c8c/coverage-7.13.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:845f352911777a8e722bfce168958214951e07e47e5d5d9744109fa5fe77f79b", size = 262584, upload-time = "2026-02-09T12:58:01.129Z" }, + { url = "https://files.pythonhosted.org/packages/22/63/01ff182fc95f260b539590fb12c11ad3e21332c15f9799cb5e2386f71d9f/coverage-7.13.4-cp313-cp313t-win32.whl", hash = "sha256:2fa8d5f8de70688a28240de9e139fa16b153cc3cbb01c5f16d88d6505ebdadf9", size = 222688, upload-time = "2026-02-09T12:58:02.736Z" }, + { url = "https://files.pythonhosted.org/packages/a9/43/89de4ef5d3cd53b886afa114065f7e9d3707bdb3e5efae13535b46ae483d/coverage-7.13.4-cp313-cp313t-win_amd64.whl", hash = "sha256:9351229c8c8407645840edcc277f4a2d44814d1bc34a2128c11c2a031d45a5dd", size = 223746, upload-time = "2026-02-09T12:58:05.362Z" }, + { url = "https://files.pythonhosted.org/packages/35/39/7cf0aa9a10d470a5309b38b289b9bb07ddeac5d61af9b664fe9775a4cb3e/coverage-7.13.4-cp313-cp313t-win_arm64.whl", hash = "sha256:30b8d0512f2dc8c8747557e8fb459d6176a2c9e5731e2b74d311c03b78451997", size = 222003, upload-time = "2026-02-09T12:58:06.952Z" }, + { url = "https://files.pythonhosted.org/packages/92/11/a9cf762bb83386467737d32187756a42094927150c3e107df4cb078e8590/coverage-7.13.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:300deaee342f90696ed186e3a00c71b5b3d27bffe9e827677954f4ee56969601", size = 219522, upload-time = "2026-02-09T12:58:08.623Z" }, + { url = "https://files.pythonhosted.org/packages/d3/28/56e6d892b7b052236d67c95f1936b6a7cf7c3e2634bf27610b8cbd7f9c60/coverage-7.13.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29e3220258d682b6226a9b0925bc563ed9a1ebcff3cad30f043eceea7eaf2689", size = 219855, upload-time = "2026-02-09T12:58:10.176Z" }, + { url = "https://files.pythonhosted.org/packages/e5/69/233459ee9eb0c0d10fcc2fe425a029b3fa5ce0f040c966ebce851d030c70/coverage-7.13.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:391ee8f19bef69210978363ca930f7328081c6a0152f1166c91f0b5fdd2a773c", size = 250887, upload-time = "2026-02-09T12:58:12.503Z" }, + { url = "https://files.pythonhosted.org/packages/06/90/2cdab0974b9b5bbc1623f7876b73603aecac11b8d95b85b5b86b32de5eab/coverage-7.13.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0dd7ab8278f0d58a0128ba2fca25824321f05d059c1441800e934ff2efa52129", size = 253396, upload-time = "2026-02-09T12:58:14.615Z" }, + { url = "https://files.pythonhosted.org/packages/ac/15/ea4da0f85bf7d7b27635039e649e99deb8173fe551096ea15017f7053537/coverage-7.13.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78cdf0d578b15148b009ccf18c686aa4f719d887e76e6b40c38ffb61d264a552", size = 254745, upload-time = "2026-02-09T12:58:16.162Z" }, + { url = "https://files.pythonhosted.org/packages/99/11/bb356e86920c655ca4d61daee4e2bbc7258f0a37de0be32d233b561134ff/coverage-7.13.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:48685fee12c2eb3b27c62f2658e7ea21e9c3239cba5a8a242801a0a3f6a8c62a", size = 257055, upload-time = "2026-02-09T12:58:17.892Z" }, + { url = "https://files.pythonhosted.org/packages/c9/0f/9ae1f8cb17029e09da06ca4e28c9e1d5c1c0a511c7074592e37e0836c915/coverage-7.13.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4e83efc079eb39480e6346a15a1bcb3e9b04759c5202d157e1dd4303cd619356", size = 250911, upload-time = "2026-02-09T12:58:19.495Z" }, + { url = "https://files.pythonhosted.org/packages/89/3a/adfb68558fa815cbc29747b553bc833d2150228f251b127f1ce97e48547c/coverage-7.13.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecae9737b72408d6a950f7e525f30aca12d4bd8dd95e37342e5beb3a2a8c4f71", size = 252754, upload-time = "2026-02-09T12:58:21.064Z" }, + { url = "https://files.pythonhosted.org/packages/32/b1/540d0c27c4e748bd3cd0bd001076ee416eda993c2bae47a73b7cc9357931/coverage-7.13.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ae4578f8528569d3cf303fef2ea569c7f4c4059a38c8667ccef15c6e1f118aa5", size = 250720, upload-time = "2026-02-09T12:58:22.622Z" }, + { url = "https://files.pythonhosted.org/packages/c7/95/383609462b3ffb1fe133014a7c84fc0dd01ed55ac6140fa1093b5af7ebb1/coverage-7.13.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6fdef321fdfbb30a197efa02d48fcd9981f0d8ad2ae8903ac318adc653f5df98", size = 254994, upload-time = "2026-02-09T12:58:24.548Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ba/1761138e86c81680bfc3c49579d66312865457f9fe405b033184e5793cb3/coverage-7.13.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b0f6ccf3dbe577170bebfce1318707d0e8c3650003cb4b3a9dd744575daa8b5", size = 250531, upload-time = "2026-02-09T12:58:26.271Z" }, + { url = "https://files.pythonhosted.org/packages/f8/8e/05900df797a9c11837ab59c4d6fe94094e029582aab75c3309a93e6fb4e3/coverage-7.13.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75fcd519f2a5765db3f0e391eb3b7d150cce1a771bf4c9f861aeab86c767a3c0", size = 252189, upload-time = "2026-02-09T12:58:27.807Z" }, + { url = "https://files.pythonhosted.org/packages/00/bd/29c9f2db9ea4ed2738b8a9508c35626eb205d51af4ab7bf56a21a2e49926/coverage-7.13.4-cp314-cp314-win32.whl", hash = "sha256:8e798c266c378da2bd819b0677df41ab46d78065fb2a399558f3f6cae78b2fbb", size = 222258, upload-time = "2026-02-09T12:58:29.441Z" }, + { url = "https://files.pythonhosted.org/packages/a7/4d/1f8e723f6829977410efeb88f73673d794075091c8c7c18848d273dc9d73/coverage-7.13.4-cp314-cp314-win_amd64.whl", hash = "sha256:245e37f664d89861cf2329c9afa2c1fe9e6d4e1a09d872c947e70718aeeac505", size = 223073, upload-time = "2026-02-09T12:58:31.026Z" }, + { url = "https://files.pythonhosted.org/packages/51/5b/84100025be913b44e082ea32abcf1afbf4e872f5120b7a1cab1d331b1e13/coverage-7.13.4-cp314-cp314-win_arm64.whl", hash = "sha256:ad27098a189e5838900ce4c2a99f2fe42a0bf0c2093c17c69b45a71579e8d4a2", size = 221638, upload-time = "2026-02-09T12:58:32.599Z" }, + { url = "https://files.pythonhosted.org/packages/a7/e4/c884a405d6ead1370433dad1e3720216b4f9fd8ef5b64bfd984a2a60a11a/coverage-7.13.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:85480adfb35ffc32d40918aad81b89c69c9cc5661a9b8a81476d3e645321a056", size = 220246, upload-time = "2026-02-09T12:58:34.181Z" }, + { url = "https://files.pythonhosted.org/packages/81/5c/4d7ed8b23b233b0fffbc9dfec53c232be2e695468523242ea9fd30f97ad2/coverage-7.13.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:79be69cf7f3bf9b0deeeb062eab7ac7f36cd4cc4c4dd694bd28921ba4d8596cc", size = 220514, upload-time = "2026-02-09T12:58:35.704Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6f/3284d4203fd2f28edd73034968398cd2d4cb04ab192abc8cff007ea35679/coverage-7.13.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:caa421e2684e382c5d8973ac55e4f36bed6821a9bad5c953494de960c74595c9", size = 261877, upload-time = "2026-02-09T12:58:37.864Z" }, + { url = "https://files.pythonhosted.org/packages/09/aa/b672a647bbe1556a85337dc95bfd40d146e9965ead9cc2fe81bde1e5cbce/coverage-7.13.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14375934243ee05f56c45393fe2ce81fe5cc503c07cee2bdf1725fb8bef3ffaf", size = 264004, upload-time = "2026-02-09T12:58:39.492Z" }, + { url = "https://files.pythonhosted.org/packages/79/a1/aa384dbe9181f98bba87dd23dda436f0c6cf2e148aecbb4e50fc51c1a656/coverage-7.13.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25a41c3104d08edb094d9db0d905ca54d0cd41c928bb6be3c4c799a54753af55", size = 266408, upload-time = "2026-02-09T12:58:41.852Z" }, + { url = "https://files.pythonhosted.org/packages/53/5e/5150bf17b4019bc600799f376bb9606941e55bd5a775dc1e096b6ffea952/coverage-7.13.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f01afcff62bf9a08fb32b2c1d6e924236c0383c02c790732b6537269e466a72", size = 267544, upload-time = "2026-02-09T12:58:44.093Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ed/f1de5c675987a4a7a672250d2c5c9d73d289dbf13410f00ed7181d8017dd/coverage-7.13.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eb9078108fbf0bcdde37c3f4779303673c2fa1fe8f7956e68d447d0dd426d38a", size = 260980, upload-time = "2026-02-09T12:58:45.721Z" }, + { url = "https://files.pythonhosted.org/packages/b3/e3/fe758d01850aa172419a6743fe76ba8b92c29d181d4f676ffe2dae2ba631/coverage-7.13.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e086334e8537ddd17e5f16a344777c1ab8194986ec533711cbe6c41cde841b6", size = 263871, upload-time = "2026-02-09T12:58:47.334Z" }, + { url = "https://files.pythonhosted.org/packages/b6/76/b829869d464115e22499541def9796b25312b8cf235d3bb00b39f1675395/coverage-7.13.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:725d985c5ab621268b2edb8e50dfe57633dc69bda071abc470fed55a14935fd3", size = 261472, upload-time = "2026-02-09T12:58:48.995Z" }, + { url = "https://files.pythonhosted.org/packages/14/9e/caedb1679e73e2f6ad240173f55218488bfe043e38da577c4ec977489915/coverage-7.13.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:3c06f0f1337c667b971ca2f975523347e63ec5e500b9aa5882d91931cd3ef750", size = 265210, upload-time = "2026-02-09T12:58:51.178Z" }, + { url = "https://files.pythonhosted.org/packages/3a/10/0dd02cb009b16ede425b49ec344aba13a6ae1dc39600840ea6abcb085ac4/coverage-7.13.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:590c0ed4bf8e85f745e6b805b2e1c457b2e33d5255dd9729743165253bc9ad39", size = 260319, upload-time = "2026-02-09T12:58:53.081Z" }, + { url = "https://files.pythonhosted.org/packages/92/8e/234d2c927af27c6d7a5ffad5bd2cf31634c46a477b4c7adfbfa66baf7ebb/coverage-7.13.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eb30bf180de3f632cd043322dad5751390e5385108b2807368997d1a92a509d0", size = 262638, upload-time = "2026-02-09T12:58:55.258Z" }, + { url = "https://files.pythonhosted.org/packages/2f/64/e5547c8ff6964e5965c35a480855911b61509cce544f4d442caa759a0702/coverage-7.13.4-cp314-cp314t-win32.whl", hash = "sha256:c4240e7eded42d131a2d2c4dec70374b781b043ddc79a9de4d55ca71f8e98aea", size = 223040, upload-time = "2026-02-09T12:58:56.936Z" }, + { url = "https://files.pythonhosted.org/packages/c7/96/38086d58a181aac86d503dfa9c47eb20715a79c3e3acbdf786e92e5c09a8/coverage-7.13.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4c7d3cc01e7350f2f0f6f7036caaf5673fb56b6998889ccfe9e1c1fe75a9c932", size = 224148, upload-time = "2026-02-09T12:58:58.645Z" }, + { url = "https://files.pythonhosted.org/packages/ce/72/8d10abd3740a0beb98c305e0c3faf454366221c0f37a8bcf8f60020bb65a/coverage-7.13.4-cp314-cp314t-win_arm64.whl", hash = "sha256:23e3f687cf945070d1c90f85db66d11e3025665d8dafa831301a0e0038f3db9b", size = 222172, upload-time = "2026-02-09T12:59:00.396Z" }, + { url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242, upload-time = "2026-02-09T12:59:02.032Z" }, +] + [[package]] name = "cryptography" version = "46.0.4" @@ -552,6 +621,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, ] +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + [[package]] name = "itsdangerous" version = "2.2.0" @@ -999,27 +1077,12 @@ wheels = [ ] [[package]] -name = "prek" -version = "0.3.2" +name = "pluggy" +version = "1.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d3/f5/ee52def928dd1355c20bcfcf765e1e61434635c33f3075e848e7b83a157b/prek-0.3.2.tar.gz", hash = "sha256:dce0074ff1a21290748ca567b4bda7553ee305a8c7b14d737e6c58364a499364", size = 334229, upload-time = "2026-02-06T13:49:47.539Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/69/70a5fc881290a63910494df2677c0fb241d27cfaa435bbcd0de5cd2e2443/prek-0.3.2-py3-none-linux_armv6l.whl", hash = "sha256:4f352f9c3fc98aeed4c8b2ec4dbf16fc386e45eea163c44d67e5571489bd8e6f", size = 4614960, upload-time = "2026-02-06T13:50:05.818Z" }, - { url = "https://files.pythonhosted.org/packages/c0/15/a82d5d32a2207ccae5d86ea9e44f2b93531ed000faf83a253e8d1108e026/prek-0.3.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:4a000cfbc3a6ec7d424f8be3c3e69ccd595448197f92daac8652382d0acc2593", size = 4622889, upload-time = "2026-02-06T13:49:53.662Z" }, - { url = "https://files.pythonhosted.org/packages/89/75/ea833b58a12741397017baef9b66a6e443bfa8286ecbd645d14111446280/prek-0.3.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5436bdc2702cbd7bcf9e355564ae66f8131211e65fefae54665a94a07c3d450a", size = 4239653, upload-time = "2026-02-06T13:50:02.88Z" }, - { url = "https://files.pythonhosted.org/packages/10/b4/d9c3885987afac6e20df4cb7db14e3b0d5a08a77ae4916488254ebac4d0b/prek-0.3.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:0161b5f584f9e7f416d6cf40a17b98f17953050ff8d8350ec60f20fe966b86b6", size = 4595101, upload-time = "2026-02-06T13:49:49.813Z" }, - { url = "https://files.pythonhosted.org/packages/21/a6/1a06473ed83dbc898de22838abdb13954e2583ce229f857f61828384634c/prek-0.3.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4e641e8533bca38797eebb49aa89ed0e8db0e61225943b27008c257e3af4d631", size = 4521978, upload-time = "2026-02-06T13:49:41.266Z" }, - { url = "https://files.pythonhosted.org/packages/0c/5e/c38390d5612e6d86b32151c1d2fdab74a57913473193591f0eb00c894c21/prek-0.3.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfca1810d49d3f9ef37599c958c4e716bc19a1d78a7e88cbdcb332e0b008994f", size = 4829108, upload-time = "2026-02-06T13:49:44.598Z" }, - { url = "https://files.pythonhosted.org/packages/80/a6/cecce2ab623747ff65ed990bb0d95fa38449ee19b348234862acf9392fff/prek-0.3.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d69d754299a95a85dc20196f633232f306bee7e7c8cba61791f49ce70404ec", size = 5357520, upload-time = "2026-02-06T13:49:48.512Z" }, - { url = "https://files.pythonhosted.org/packages/a5/18/d6bcb29501514023c76d55d5cd03bdbc037737c8de8b6bc41cdebfb1682c/prek-0.3.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:539dcb90ad9b20837968539855df6a29493b328a1ae87641560768eed4f313b0", size = 4852635, upload-time = "2026-02-06T13:49:58.347Z" }, - { url = "https://files.pythonhosted.org/packages/1b/0a/ae46f34ba27ba87aea5c9ad4ac9cd3e07e014fd5079ae079c84198f62118/prek-0.3.2-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:1998db3d0cbe243984736c82232be51318f9192e2433919a6b1c5790f600b5fd", size = 4599484, upload-time = "2026-02-06T13:49:43.296Z" }, - { url = "https://files.pythonhosted.org/packages/1a/a9/73bfb5b3f7c3583f9b0d431924873928705cdef6abb3d0461c37254a681b/prek-0.3.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:07ab237a5415a3e8c0db54de9d63899bcd947624bdd8820d26f12e65f8d19eb7", size = 4657694, upload-time = "2026-02-06T13:50:01.074Z" }, - { url = "https://files.pythonhosted.org/packages/a7/bc/0994bc176e1a80110fad3babce2c98b0ac4007630774c9e18fc200a34781/prek-0.3.2-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:0ced19701d69c14a08125f14a5dd03945982edf59e793c73a95caf4697a7ac30", size = 4509337, upload-time = "2026-02-06T13:49:54.891Z" }, - { url = "https://files.pythonhosted.org/packages/f9/13/e73f85f65ba8f626468e5d1694ab3763111513da08e0074517f40238c061/prek-0.3.2-py3-none-musllinux_1_1_i686.whl", hash = "sha256:ffb28189f976fa111e770ee94e4f298add307714568fb7d610c8a7095cb1ce59", size = 4697350, upload-time = "2026-02-06T13:50:04.526Z" }, - { url = "https://files.pythonhosted.org/packages/14/47/98c46dcd580305b9960252a4eb966f1a7b1035c55c363f378d85662ba400/prek-0.3.2-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:f63134b3eea14421789a7335d86f99aee277cb520427196f2923b9260c60e5c5", size = 4955860, upload-time = "2026-02-06T13:49:56.581Z" }, - { url = "https://files.pythonhosted.org/packages/73/42/1bb4bba3ff47897df11e9dfd774027cdfa135482c961a54e079af0faf45a/prek-0.3.2-py3-none-win32.whl", hash = "sha256:58c806bd1344becd480ef5a5ba348846cc000af0e1fbe854fef91181a2e06461", size = 4267619, upload-time = "2026-02-06T13:49:39.503Z" }, - { url = "https://files.pythonhosted.org/packages/97/11/6665f47a7c350d83de17403c90bbf7a762ef50876ece456a86f64f46fbfb/prek-0.3.2-py3-none-win_amd64.whl", hash = "sha256:70114b48e9eb8048b2c11b4c7715ce618529c6af71acc84dd8877871a2ef71a6", size = 4624324, upload-time = "2026-02-06T13:49:45.922Z" }, - { url = "https://files.pythonhosted.org/packages/22/e7/740997ca82574d03426f897fd88afe3fc8a7306b8c7ea342a8bc1c538488/prek-0.3.2-py3-none-win_arm64.whl", hash = "sha256:9144d176d0daa2469a25c303ef6f6fa95a8df015eb275232f5cb53551ecefef0", size = 4336008, upload-time = "2026-02-06T13:49:52.27Z" }, + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] [[package]] @@ -1285,6 +1348,36 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b9/f9/c9757a984c4ffb6d12fab69e966d95dfc862a5d44e12b7900f3a03780b76/pyside6_essentials-6.10.2-cp39-abi3-win_arm64.whl", hash = "sha256:db5f4913648bb6afddb8b347edae151ee2378f12bceb03c8b2515a530a4b38d9", size = 55258626, upload-time = "2026-02-02T08:46:36.788Z" }, ] +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + +[[package]] +name = "pytest-cov" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -1571,8 +1664,10 @@ dev = [ { name = "bump-my-version" }, { name = "icecream" }, { name = "nuitka" }, - { name = "prek" }, { name = "pyinstaller" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "ty" }, ] swbtest = [ { name = "alive-progress" }, @@ -1613,8 +1708,10 @@ dev = [ { name = "bump-my-version", specifier = ">=0.29.0" }, { name = "icecream", specifier = ">=2.1.4" }, { name = "nuitka", specifier = ">=2.5.9" }, - { name = "prek", specifier = ">=0.3.2" }, { name = "pyinstaller", specifier = ">=6.17.0" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "ty", specifier = ">=0.0.15" }, ] swbtest = [{ name = "alive-progress", specifier = ">=3.3.0" }] @@ -1693,6 +1790,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, ] +[[package]] +name = "ty" +version = "0.0.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/25/257602d316b9333089b688a7a11b33ebc660b74e8dacf400dc3dfdea1594/ty-0.0.15.tar.gz", hash = "sha256:4f9a5b8df208c62dba56e91b93bed8b5bb714839691b8cff16d12c983bfa1174", size = 5101936, upload-time = "2026-02-05T01:06:34.922Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/c5/35626e732b79bf0e6213de9f79aff59b5f247c0a1e3ce0d93e675ab9b728/ty-0.0.15-py3-none-linux_armv6l.whl", hash = "sha256:68e092458516c61512dac541cde0a5e4e5842df00b4e81881ead8f745ddec794", size = 10138374, upload-time = "2026-02-05T01:07:03.804Z" }, + { url = "https://files.pythonhosted.org/packages/d5/8a/48fd81664604848f79d03879b3ca3633762d457a069b07e09fb1b87edd6e/ty-0.0.15-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:79f2e75289eae3cece94c51118b730211af4ba5762906f52a878041b67e54959", size = 9947858, upload-time = "2026-02-05T01:06:47.453Z" }, + { url = "https://files.pythonhosted.org/packages/b6/85/c1ac8e97bcd930946f4c94db85b675561d590b4e72703bf3733419fc3973/ty-0.0.15-py3-none-macosx_11_0_arm64.whl", hash = "sha256:112a7b26e63e48cc72c8c5b03227d1db280cfa57a45f2df0e264c3a016aa8c3c", size = 9443220, upload-time = "2026-02-05T01:06:44.98Z" }, + { url = "https://files.pythonhosted.org/packages/3c/d9/244bc02599d950f7a4298fbc0c1b25cc808646b9577bdf7a83470b2d1cec/ty-0.0.15-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71f62a2644972975a657d9dc867bf901235cde51e8d24c20311067e7afd44a56", size = 9949976, upload-time = "2026-02-05T01:07:01.515Z" }, + { url = "https://files.pythonhosted.org/packages/7e/ab/3a0daad66798c91a33867a3ececf17d314ac65d4ae2bbbd28cbfde94da63/ty-0.0.15-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9e48b42be2d257317c85b78559233273b655dd636fc61e7e1d69abd90fd3cba4", size = 9965918, upload-time = "2026-02-05T01:06:54.283Z" }, + { url = "https://files.pythonhosted.org/packages/39/4e/e62b01338f653059a7c0cd09d1a326e9a9eedc351a0f0de9db0601658c3d/ty-0.0.15-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27dd5b52a421e6871c5bfe9841160331b60866ed2040250cb161886478ab3e4f", size = 10424943, upload-time = "2026-02-05T01:07:08.777Z" }, + { url = "https://files.pythonhosted.org/packages/65/b5/7aa06655ce69c0d4f3e845d2d85e79c12994b6d84c71699cfb437e0bc8cf/ty-0.0.15-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76b85c9ec2219e11c358a7db8e21b7e5c6674a1fb9b6f633836949de98d12286", size = 10964692, upload-time = "2026-02-05T01:06:37.103Z" }, + { url = "https://files.pythonhosted.org/packages/13/04/36fdfe1f3c908b471e246e37ce3d011175584c26d3853e6c5d9a0364564c/ty-0.0.15-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9e8204c61d8ede4f21f2975dce74efdb80fafb2fae1915c666cceb33ea3c90b", size = 10692225, upload-time = "2026-02-05T01:06:49.714Z" }, + { url = "https://files.pythonhosted.org/packages/13/41/5bf882649bd8b64ded5fbce7fb8d77fb3b868de1a3b1a6c4796402b47308/ty-0.0.15-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af87c3be7c944bb4d6609d6c63e4594944b0028c7bd490a525a82b88fe010d6d", size = 10516776, upload-time = "2026-02-05T01:06:52.047Z" }, + { url = "https://files.pythonhosted.org/packages/56/75/66852d7e004f859839c17ffe1d16513c1e7cc04bcc810edb80ca022a9124/ty-0.0.15-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:50dccf7398505e5966847d366c9e4c650b8c225411c2a68c32040a63b9521eea", size = 9928828, upload-time = "2026-02-05T01:06:56.647Z" }, + { url = "https://files.pythonhosted.org/packages/65/72/96bc16c7b337a3ef358fd227b3c8ef0c77405f3bfbbfb59ee5915f0d9d71/ty-0.0.15-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:bd797b8f231a4f4715110259ad1ad5340a87b802307f3e06d92bfb37b858a8f3", size = 9978960, upload-time = "2026-02-05T01:06:29.567Z" }, + { url = "https://files.pythonhosted.org/packages/a0/18/d2e316a35b626de2227f832cd36d21205e4f5d96fd036a8af84c72ecec1b/ty-0.0.15-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9deb7f20e18b25440a9aa4884f934ba5628ef456dbde91819d5af1a73da48af3", size = 10135903, upload-time = "2026-02-05T01:06:59.256Z" }, + { url = "https://files.pythonhosted.org/packages/02/d3/b617a79c9dad10c888d7c15cd78859e0160b8772273637b9c4241a049491/ty-0.0.15-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7b31b3de031255b90a5f4d9cb3d050feae246067c87130e5a6861a8061c71754", size = 10615879, upload-time = "2026-02-05T01:07:06.661Z" }, + { url = "https://files.pythonhosted.org/packages/fb/b0/2652a73c71c77296a6343217063f05745da60c67b7e8a8e25f2064167fce/ty-0.0.15-py3-none-win32.whl", hash = "sha256:9362c528ceb62c89d65c216336d28d500bc9f4c10418413f63ebc16886e16cc1", size = 9578058, upload-time = "2026-02-05T01:06:42.928Z" }, + { url = "https://files.pythonhosted.org/packages/84/6e/08a4aedebd2a6ce2784b5bc3760e43d1861f1a184734a78215c2d397c1df/ty-0.0.15-py3-none-win_amd64.whl", hash = "sha256:4db040695ae67c5524f59cb8179a8fa277112e69042d7dfdac862caa7e3b0d9c", size = 10457112, upload-time = "2026-02-05T01:06:39.885Z" }, + { url = "https://files.pythonhosted.org/packages/b3/be/1991f2bc12847ae2d4f1e3ac5dcff8bb7bc1261390645c0755bb55616355/ty-0.0.15-py3-none-win_arm64.whl", hash = "sha256:e5a98d4119e77d6136461e16ae505f8f8069002874ab073de03fbcb1a5e8bf25", size = 9937490, upload-time = "2026-02-05T01:06:32.388Z" }, +] + [[package]] name = "typing-extensions" version = "4.15.0"