From ebdf29cdbd0f3a4e404229360839940b96bf42a0 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 25 Aug 2026 16:53:28 +1000 Subject: [PATCH 1/3] New 'Earnings' interface for viewing/tracking earnings across reporting periods (e.g for BAS) --- CHANGELOG.md | 4 + bouquin/db.py | 302 ++++++++++++ bouquin/earnings.py | 920 +++++++++++++++++++++++++++++++++++++ bouquin/invoices.py | 95 +++- bouquin/locales/en.json | 46 +- bouquin/settings.py | 3 + bouquin/settings_dialog.py | 10 + bouquin/time_log.py | 9 + tests/test_earnings.py | 173 +++++++ tests/test_settings.py | 3 + 10 files changed, 1561 insertions(+), 4 deletions(-) create mode 100644 bouquin/earnings.py create mode 100644 tests/test_earnings.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 19290cb..4f88ee4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.10.0 + + * New 'Earnings' interface for viewing/tracking earnings across reporting periods (e.g for BAS) + # 0.9.0 * Add 'Projects' interface for unified time/invoice/docs view. diff --git a/bouquin/db.py b/bouquin/db.py index 35888a9..f0e1d6d 100644 --- a/bouquin/db.py +++ b/bouquin/db.py @@ -100,6 +100,7 @@ class DBConfig: reminders_webhook_secret: str = (None,) documents: bool = True invoicing: bool = False + reporting_currency: str = "AUD" locale: str = "en" font_size: int = 11 @@ -120,6 +121,9 @@ class DBManager: "detail_mode", "paid_at", "payment_note", + "reporting_currency", + "reporting_total_cents", + "reporting_note", "document_id", } ) @@ -369,6 +373,9 @@ class DBManager: detail_mode TEXT NOT NULL, -- 'detailed' | 'summary' paid_at TEXT, payment_note TEXT, + reporting_currency TEXT, + reporting_total_cents INTEGER, + reporting_note TEXT, document_id INTEGER, created_at TEXT NOT NULL DEFAULT ( strftime('%Y-%m-%dT%H:%M:%fZ','now') @@ -401,6 +408,25 @@ class DBManager: REFERENCES time_log(id) ON DELETE RESTRICT, PRIMARY KEY (invoice_id, time_log_id) ); + + CREATE TABLE IF NOT EXISTS invoice_payments ( + id INTEGER PRIMARY KEY, + invoice_id INTEGER NOT NULL + REFERENCES invoices(id) ON DELETE CASCADE, + received_at TEXT NOT NULL, -- yyyy-MM-dd + invoice_amount_cents INTEGER NOT NULL, + reporting_currency TEXT NOT NULL, + reporting_amount_cents INTEGER NOT NULL, + note TEXT, + created_at TEXT NOT NULL DEFAULT ( + strftime('%Y-%m-%dT%H:%M:%fZ','now') + ) + ); + + CREATE INDEX IF NOT EXISTS ix_invoice_payments_invoice + ON invoice_payments(invoice_id, received_at); + CREATE INDEX IF NOT EXISTS ix_invoice_payments_received + ON invoice_payments(received_at); """ ) self._ensure_column( @@ -408,6 +434,21 @@ class DBManager: "created_at", "created_at TEXT", ) + self._ensure_column( + "invoices", + "reporting_currency", + "reporting_currency TEXT", + ) + self._ensure_column( + "invoices", + "reporting_total_cents", + "reporting_total_cents INTEGER", + ) + self._ensure_column( + "invoices", + "reporting_note", + "reporting_note TEXT", + ) self.conn.commit() def _ensure_column(self, table: str, column: str, definition: str) -> None: @@ -2924,6 +2965,252 @@ class DBManager: ).fetchall() return rows + def get_invoice_with_project(self, invoice_id: int): + return self.conn.execute( + """ + SELECT + i.*, + p.name AS project_name + FROM invoices AS i + LEFT JOIN projects AS p ON p.id = i.project_id + WHERE i.id = ? + """, + (invoice_id,), + ).fetchone() + + def set_invoice_reporting_value( + self, + invoice_id: int, + reporting_currency: str, + reporting_total_cents: int, + note: str | None = None, + ) -> None: + """Store the invoice-date value used by invoice-basis earnings reports.""" + invoice = self.get_invoice_with_project(invoice_id) + if invoice is None: + raise ValueError("Invoice does not exist.") + reporting_currency = reporting_currency.strip().upper() + if not reporting_currency: + raise ValueError("A reporting currency is required.") + if reporting_total_cents <= 0: + raise ValueError("The reporting total must be greater than zero.") + with self.conn: + self.conn.execute( + """ + UPDATE invoices + SET reporting_currency = ?, + reporting_total_cents = ?, + reporting_note = ? + WHERE id = ? + """, + ( + reporting_currency, + int(reporting_total_cents), + note.strip() if note and note.strip() else None, + invoice_id, + ), + ) + + def clear_invoice_reporting_value(self, invoice_id: int) -> None: + with self.conn: + self.conn.execute( + """ + UPDATE invoices + SET reporting_currency = NULL, + reporting_total_cents = NULL, + reporting_note = NULL + WHERE id = ? + """, + (invoice_id,), + ) + + def get_invoices_for_earnings_range(self, start_date_iso: str, end_date_iso: str): + """Return invoices by issue date for invoice-basis earnings reporting.""" + return self.conn.execute( + """ + SELECT + i.id AS invoice_id, + i.issue_date, + i.invoice_number, + i.currency, + i.tax_label, + i.tax_rate_percent, + i.subtotal_cents, + i.tax_cents, + i.total_cents, + i.reporting_currency, + i.reporting_total_cents, + i.reporting_note, + p.name AS project_name, + pb.client_company + FROM invoices AS i + LEFT JOIN projects AS p ON p.id = i.project_id + LEFT JOIN project_billing AS pb ON pb.project_id = i.project_id + WHERE i.issue_date BETWEEN ? AND ? + ORDER BY i.issue_date, LOWER(p.name), i.invoice_number + """, + (start_date_iso, end_date_iso), + ).fetchall() + + def get_invoice_payments(self, invoice_id: int): + return self.conn.execute( + """ + SELECT * + FROM invoice_payments + WHERE invoice_id = ? + ORDER BY received_at, id + """, + (invoice_id,), + ).fetchall() + + def get_invoice_payment_applied_cents(self, invoice_id: int) -> int: + row = self.conn.execute( + """ + SELECT COALESCE(SUM(invoice_amount_cents), 0) AS total + FROM invoice_payments + WHERE invoice_id = ? + """, + (invoice_id,), + ).fetchone() + return int(row["total"] or 0) + + def _sync_invoice_paid_at_from_payments(self, invoice_id: int) -> None: + invoice = self.get_invoice_with_project(invoice_id) + if invoice is None: + return + paid = self.get_invoice_payment_applied_cents(invoice_id) + total = int(invoice["total_cents"] or 0) + if total > 0 and paid >= total: + row = self.conn.execute( + """ + SELECT MAX(received_at) AS paid_at + FROM invoice_payments + WHERE invoice_id = ? + """, + (invoice_id,), + ).fetchone() + paid_at = row["paid_at"] if row else None + else: + paid_at = None + self.conn.execute( + "UPDATE invoices SET paid_at = ? WHERE id = ?", + (paid_at, invoice_id), + ) + + def add_invoice_payment( + self, + invoice_id: int, + received_at: str, + invoice_amount_cents: int, + reporting_currency: str, + reporting_amount_cents: int, + note: str | None = None, + ) -> int: + invoice = self.get_invoice_with_project(invoice_id) + if invoice is None: + raise ValueError("Invoice does not exist.") + if invoice_amount_cents <= 0 or reporting_amount_cents <= 0: + raise ValueError("Payment amounts must be greater than zero.") + reporting_currency = reporting_currency.strip().upper() + if not reporting_currency: + raise ValueError("A reporting currency is required.") + try: + _dt.date.fromisoformat(received_at) + except ValueError as exc: + raise ValueError("Payment date must use YYYY-MM-DD format.") from exc + + total = int(invoice["total_cents"] or 0) + already_applied = self.get_invoice_payment_applied_cents(invoice_id) + if already_applied + invoice_amount_cents > total: + raise ValueError("Payment amount exceeds the outstanding invoice balance.") + + with self.conn: + cur = self.conn.execute( + """ + INSERT INTO invoice_payments ( + invoice_id, + received_at, + invoice_amount_cents, + reporting_currency, + reporting_amount_cents, + note + ) VALUES (?, ?, ?, ?, ?, ?) + """, + ( + invoice_id, + received_at, + invoice_amount_cents, + reporting_currency, + reporting_amount_cents, + note, + ), + ) + payment_id = int(cur.lastrowid) + self._sync_invoice_paid_at_from_payments(invoice_id) + return payment_id + + def delete_invoice_payment(self, payment_id: int) -> None: + row = self.conn.execute( + "SELECT invoice_id FROM invoice_payments WHERE id = ?", + (payment_id,), + ).fetchone() + if row is None: + return + invoice_id = int(row["invoice_id"]) + with self.conn: + self.conn.execute( + "DELETE FROM invoice_payments WHERE id = ?", (payment_id,) + ) + self._sync_invoice_paid_at_from_payments(invoice_id) + + def get_payments_for_range(self, start_date_iso: str, end_date_iso: str): + return self.conn.execute( + """ + SELECT + ip.id AS payment_id, + ip.received_at, + ip.invoice_amount_cents, + ip.reporting_currency, + ip.reporting_amount_cents, + ip.note, + i.id AS invoice_id, + i.invoice_number, + i.currency, + i.tax_label, + i.tax_rate_percent, + i.tax_cents, + i.total_cents, + p.name AS project_name, + pb.client_company + FROM invoice_payments AS ip + JOIN invoices AS i ON i.id = ip.invoice_id + LEFT JOIN projects AS p ON p.id = i.project_id + LEFT JOIN project_billing AS pb ON pb.project_id = i.project_id + WHERE ip.received_at BETWEEN ? AND ? + ORDER BY ip.received_at, LOWER(p.name), i.invoice_number, ip.id + """, + (start_date_iso, end_date_iso), + ).fetchall() + + def get_paid_invoices_without_payments( + self, start_date_iso: str, end_date_iso: str + ): + return self.conn.execute( + """ + SELECT i.*, p.name AS project_name + FROM invoices AS i + LEFT JOIN projects AS p ON p.id = i.project_id + WHERE i.paid_at BETWEEN ? AND ? + AND NOT EXISTS ( + SELECT 1 + FROM invoice_payments AS ip + WHERE ip.invoice_id = i.id + ) + ORDER BY i.paid_at, LOWER(p.name), i.invoice_number + """, + (start_date_iso, end_date_iso), + ).fetchall() + def _validate_invoice_field(self, field: str) -> str: if field not in self._INVOICE_COLUMN_ALLOWLIST: raise ValueError(f"Invalid invoice field name: {field!r}") @@ -2952,6 +3239,21 @@ class DBManager: invoice_id, ), ) + # A foreign-currency reporting valuation is tied to the invoice's + # issue date, currency and gross total. If one of those changes, + # require the user to value the invoice again rather than silently + # retaining stale tax-reporting data. + if field in {"issue_date", "currency", "total_cents"}: + self.conn.execute( + """ + UPDATE invoices + SET reporting_currency = NULL, + reporting_total_cents = NULL, + reporting_note = NULL + WHERE id = ? + """, + (invoice_id,), + ) def update_invoice_number(self, invoice_id: int, invoice_number: str) -> None: with self.conn: diff --git a/bouquin/earnings.py b/bouquin/earnings.py new file mode 100644 index 0000000..2643aac --- /dev/null +++ b/bouquin/earnings.py @@ -0,0 +1,920 @@ +from __future__ import annotations + +import csv +from collections import OrderedDict +from dataclasses import dataclass +from datetime import date +from pathlib import Path + +from PySide6.QtCore import QDate, QRectF, Qt, Signal +from PySide6.QtGui import QPainter, QPen +from PySide6.QtWidgets import ( + QAbstractItemView, + QComboBox, + QDateEdit, + QDialog, + QDoubleSpinBox, + QFileDialog, + QFormLayout, + QHBoxLayout, + QHeaderView, + QLabel, + QLineEdit, + QMessageBox, + QPushButton, + QTableWidget, + QTableWidgetItem, + QTextEdit, + QVBoxLayout, + QWidget, +) + +from . import strings +from .db import DBManager +from .settings import load_db_config + + +@dataclass(frozen=True) +class MonthlyEarnings: + month: str + sales_ex_tax_cents: int = 0 + tax_cents: int = 0 + sales_inc_tax_cents: int = 0 + entry_count: int = 0 + + +def _month_key(date_iso: str) -> str: + return date_iso[:7] + + +def _iter_months(start_iso: str, end_iso: str): + start = date.fromisoformat(start_iso) + end = date.fromisoformat(end_iso) + year, month = start.year, start.month + while (year, month) <= (end.year, end.month): + yield f"{year:04d}-{month:02d}" + if month == 12: + year += 1 + month = 1 + else: + month += 1 + + +def aggregate_payments_by_month(rows, start_iso: str, end_iso: str): + """Aggregate structured payment rows into reporting-currency monthly totals. + + Each invoice currently has a single invoice-wide tax rate, so a part-payment's + tax component is the same proportion of the reporting-currency receipt as the + invoice tax is of the invoice total. + """ + totals = OrderedDict( + (month, [0, 0, 0, 0]) for month in _iter_months(start_iso, end_iso) + ) + + for row in rows: + gross = int(row["reporting_amount_cents"] or 0) + invoice_total = int(row["total_cents"] or 0) + invoice_tax = int(row["tax_cents"] or 0) + tax = int(round(gross * invoice_tax / invoice_total)) if invoice_total else 0 + net = gross - tax + bucket = totals.setdefault(_month_key(row["received_at"]), [0, 0, 0, 0]) + bucket[0] += net + bucket[1] += tax + bucket[2] += gross + bucket[3] += 1 + + return [ + MonthlyEarnings( + month=month, + sales_ex_tax_cents=values[0], + tax_cents=values[1], + sales_inc_tax_cents=values[2], + entry_count=values[3], + ) + for month, values in totals.items() + ] + + +def invoice_reporting_amount_cents(row, reporting_currency: str) -> int | None: + """Return an invoice's gross value in the requested reporting currency. + + Same-currency invoices are exact and need no extra valuation. Foreign-currency + invoices require an explicit invoice-date reporting value so a later bank + receipt is never silently reused as the tax/reporting value. + """ + currency = str(row["currency"] or "").strip().upper() + requested = reporting_currency.strip().upper() + if currency == requested: + return int(row["total_cents"] or 0) + + stored_currency = str(row["reporting_currency"] or "").strip().upper() + stored_total = row["reporting_total_cents"] + if stored_currency == requested and stored_total is not None: + return int(stored_total) + return None + + +def aggregate_invoices_by_month( + rows, reporting_currency: str, start_iso: str, end_iso: str +): + """Aggregate invoices by issue month in one reporting currency.""" + totals = OrderedDict( + (month, [0, 0, 0, 0]) for month in _iter_months(start_iso, end_iso) + ) + + for row in rows: + gross = invoice_reporting_amount_cents(row, reporting_currency) + if gross is None: + continue + invoice_total = int(row["total_cents"] or 0) + invoice_tax = int(row["tax_cents"] or 0) + tax = int(round(gross * invoice_tax / invoice_total)) if invoice_total else 0 + net = gross - tax + bucket = totals.setdefault(_month_key(row["issue_date"]), [0, 0, 0, 0]) + bucket[0] += net + bucket[1] += tax + bucket[2] += gross + bucket[3] += 1 + + return [ + MonthlyEarnings( + month=month, + sales_ex_tax_cents=values[0], + tax_cents=values[1], + sales_inc_tax_cents=values[2], + entry_count=values[3], + ) + for month, values in totals.items() + ] + + +class InvoiceReportingValueDialog(QDialog): + """Record the invoice-date value of a foreign-currency invoice.""" + + valueChanged = Signal() + + def __init__(self, db: DBManager, invoice_id: int, parent=None): + super().__init__(parent) + self._db = db + self._invoice_id = int(invoice_id) + self.cfg = load_db_config() + self._invoice = self._db.get_invoice_with_project(self._invoice_id) + if self._invoice is None: + raise ValueError(f"Invoice {invoice_id} does not exist") + + self.setWindowTitle( + strings._("invoice_reporting_value_title").format( + invoice=self._invoice["invoice_number"] or "?", + project=self._invoice["project_name"] or "", + ) + ) + self.resize(600, 330) + root = QVBoxLayout(self) + + summary = QLabel( + strings._("invoice_reporting_value_summary").format( + issue_date=self._invoice["issue_date"] or "", + total=int(self._invoice["total_cents"] or 0) / 100.0, + currency=self._invoice["currency"] or "", + ) + ) + summary.setWordWrap(True) + root.addWidget(summary) + + form = QFormLayout() + self.reporting_currency = QLineEdit( + self._invoice["reporting_currency"] or self.cfg.reporting_currency or "AUD" + ) + self.reporting_currency.setMaxLength(8) + form.addRow(strings._("reporting_currency") + ":", self.reporting_currency) + + self.reporting_total = QDoubleSpinBox() + self.reporting_total.setDecimals(2) + self.reporting_total.setMaximum(999999999.99) + if self._invoice["reporting_total_cents"] is not None: + self.reporting_total.setValue( + int(self._invoice["reporting_total_cents"]) / 100.0 + ) + elif ( + str(self._invoice["currency"] or "").upper() + == str(self.reporting_currency.text() or "").upper() + ): + self.reporting_total.setValue( + int(self._invoice["total_cents"] or 0) / 100.0 + ) + form.addRow(strings._("invoice_reporting_total") + ":", self.reporting_total) + + self.note = QTextEdit() + self.note.setMaximumHeight(90) + self.note.setPlainText(self._invoice["reporting_note"] or "") + form.addRow(strings._("invoice_reporting_note") + ":", self.note) + root.addLayout(form) + + help_label = QLabel(strings._("invoice_reporting_value_help")) + help_label.setWordWrap(True) + root.addWidget(help_label) + + buttons = QHBoxLayout() + clear_btn = QPushButton(strings._("invoice_reporting_value_clear")) + clear_btn.clicked.connect(self._clear) + buttons.addWidget(clear_btn) + buttons.addStretch(1) + save_btn = QPushButton(strings._("save")) + save_btn.clicked.connect(self._save) + buttons.addWidget(save_btn) + close_btn = QPushButton(strings._("close")) + close_btn.clicked.connect(self.accept) + buttons.addWidget(close_btn) + root.addLayout(buttons) + + def _save(self) -> None: + currency = self.reporting_currency.text().strip().upper() + amount_cents = int(round(self.reporting_total.value() * 100)) + if not currency or amount_cents <= 0: + QMessageBox.warning( + self, strings._("error"), strings._("invoice_reporting_value_required") + ) + return + self._db.set_invoice_reporting_value( + self._invoice_id, + currency, + amount_cents, + self.note.toPlainText(), + ) + self.reporting_currency.setText(currency) + self.valueChanged.emit() + self.accept() + + def _clear(self) -> None: + self._db.clear_invoice_reporting_value(self._invoice_id) + self.valueChanged.emit() + self.accept() + + +class EarningsChart(QWidget): + """Small stacked monthly sales chart: ex-tax sales plus tax.""" + + def __init__(self, parent=None): + super().__init__(parent) + self._rows: list[MonthlyEarnings] = [] + self.setMinimumHeight(190) + + def set_rows(self, rows: list[MonthlyEarnings]) -> None: + self._rows = rows + self.update() + + def paintEvent(self, event): # noqa: N802 - Qt API + _ = event + painter = QPainter(self) + painter.setRenderHint(QPainter.RenderHint.Antialiasing, True) + palette = self.palette() + painter.fillRect(self.rect(), palette.base()) + + if not self._rows: + painter.setPen(palette.text().color()) + painter.drawText( + self.rect(), Qt.AlignmentFlag.AlignCenter, strings._("earnings_no_data") + ) + return + + left, top, right, bottom = 56, 12, 16, 36 + plot_w = max(1, self.width() - left - right) + plot_h = max(1, self.height() - top - bottom) + max_gross = max((r.sales_inc_tax_cents for r in self._rows), default=0) + if max_gross <= 0: + painter.setPen(palette.text().color()) + painter.drawText( + self.rect(), Qt.AlignmentFlag.AlignCenter, strings._("earnings_no_data") + ) + return + + axis_pen = QPen(palette.mid().color()) + painter.setPen(axis_pen) + painter.drawLine(left, top, left, top + plot_h) + painter.drawLine(left, top + plot_h, left + plot_w, top + plot_h) + + count = max(1, len(self._rows)) + slot = plot_w / count + bar_w = max(8.0, min(54.0, slot * 0.58)) + net_color = palette.highlight().color() + tax_color = palette.mid().color() + + for idx, row in enumerate(self._rows): + x = left + slot * idx + (slot - bar_w) / 2 + gross_h = (row.sales_inc_tax_cents / max_gross) * plot_h + tax_h = (row.tax_cents / max_gross) * plot_h + net_h = max(0.0, gross_h - tax_h) + base_y = top + plot_h + + painter.fillRect(QRectF(x, base_y - net_h, bar_w, net_h), net_color) + if tax_h > 0: + painter.fillRect(QRectF(x, base_y - gross_h, bar_w, tax_h), tax_color) + + painter.setPen(palette.text().color()) + label = row.month[5:7] + "/" + row.month[2:4] + painter.drawText( + QRectF(left + slot * idx, base_y + 4, slot, 24), + Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop, + label, + ) + + painter.setPen(palette.text().color()) + painter.drawText(4, top + 12, f"{max_gross / 100.0:,.0f}") + painter.drawText(4, top + plot_h, "0") + + +class PaymentsDialog(QDialog): + """Record and manage structured receipts for one invoice.""" + + paymentsChanged = Signal() + + COL_DATE = 0 + COL_INVOICE_AMOUNT = 1 + COL_REPORTING_AMOUNT = 2 + COL_RATE = 3 + COL_NOTE = 4 + + def __init__(self, db: DBManager, invoice_id: int, parent=None): + super().__init__(parent) + self._db = db + self._invoice_id = int(invoice_id) + self.cfg = load_db_config() + self._invoice = self._db.get_invoice_with_project(self._invoice_id) + if self._invoice is None: + raise ValueError(f"Invoice {invoice_id} does not exist") + + title = strings._("invoice_payments_title").format( + invoice=self._invoice["invoice_number"] or "?", + project=self._invoice["project_name"] or "", + ) + self.setWindowTitle(title) + self.resize(820, 520) + + root = QVBoxLayout(self) + summary = QLabel( + strings._("invoice_payments_summary").format( + total=(int(self._invoice["total_cents"] or 0) / 100.0), + currency=self._invoice["currency"] or "", + ) + ) + root.addWidget(summary) + + form = QFormLayout() + self.received_at = QDateEdit(QDate.currentDate()) + self.received_at.setCalendarPopup(True) + self.received_at.setDisplayFormat("yyyy-MM-dd") + if self._invoice["paid_at"]: + qd = QDate.fromString(str(self._invoice["paid_at"]), "yyyy-MM-dd") + if qd.isValid(): + self.received_at.setDate(qd) + form.addRow(strings._("invoice_payment_received_on") + ":", self.received_at) + + self.invoice_amount = QDoubleSpinBox() + self.invoice_amount.setDecimals(2) + self.invoice_amount.setMaximum(999999999.99) + self.invoice_amount.setSuffix(f" {self._invoice['currency'] or ''}") + form.addRow( + strings._("invoice_payment_applied_amount") + ":", self.invoice_amount + ) + + self.reporting_currency = QLineEdit(self.cfg.reporting_currency or "AUD") + self.reporting_currency.setMaxLength(8) + form.addRow(strings._("reporting_currency") + ":", self.reporting_currency) + + self.reporting_amount = QDoubleSpinBox() + self.reporting_amount.setDecimals(2) + self.reporting_amount.setMaximum(999999999.99) + form.addRow( + strings._("invoice_payment_reporting_amount") + ":", self.reporting_amount + ) + + self.note = QTextEdit() + self.note.setMaximumHeight(72) + form.addRow(strings._("invoice_payment_note") + ":", self.note) + root.addLayout(form) + + self.help_label = QLabel(strings._("invoice_payment_reporting_help")) + self.help_label.setWordWrap(True) + root.addWidget(self.help_label) + + add_row = QHBoxLayout() + self.outstanding_label = QLabel("") + add_row.addWidget(self.outstanding_label) + add_row.addStretch(1) + add_btn = QPushButton(strings._("invoice_payment_record")) + add_btn.clicked.connect(self._record_payment) + add_row.addWidget(add_btn) + root.addLayout(add_row) + + self.table = QTableWidget() + self.table.setColumnCount(5) + self.table.setHorizontalHeaderLabels( + [ + strings._("invoice_payment_received_on"), + strings._("invoice_payment_applied_amount"), + strings._("invoice_payment_reporting_amount"), + strings._("invoice_payment_exchange_rate"), + strings._("invoice_payment_note"), + ] + ) + self.table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) + self.table.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection) + self.table.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers) + header = self.table.horizontalHeader() + header.setSectionResizeMode( + self.COL_DATE, QHeaderView.ResizeMode.ResizeToContents + ) + header.setSectionResizeMode( + self.COL_INVOICE_AMOUNT, QHeaderView.ResizeMode.ResizeToContents + ) + header.setSectionResizeMode( + self.COL_REPORTING_AMOUNT, QHeaderView.ResizeMode.ResizeToContents + ) + header.setSectionResizeMode( + self.COL_RATE, QHeaderView.ResizeMode.ResizeToContents + ) + header.setSectionResizeMode(self.COL_NOTE, QHeaderView.ResizeMode.Stretch) + root.addWidget(self.table, 1) + + btn_row = QHBoxLayout() + delete_btn = QPushButton(strings._("delete")) + delete_btn.clicked.connect(self._delete_payment) + btn_row.addWidget(delete_btn) + btn_row.addStretch(1) + close_btn = QPushButton(strings._("close")) + close_btn.clicked.connect(self.accept) + btn_row.addWidget(close_btn) + root.addLayout(btn_row) + + self.invoice_amount.valueChanged.connect(self._copy_same_currency_amount) + self.reporting_currency.textChanged.connect(self._copy_same_currency_amount) + self._reload() + + def _outstanding_cents(self) -> int: + total = int(self._invoice["total_cents"] or 0) + applied = self._db.get_invoice_payment_applied_cents(self._invoice_id) + return max(0, total - applied) + + def _copy_same_currency_amount(self, *_args) -> None: + if ( + self.reporting_currency.text().strip().upper() + == str(self._invoice["currency"] or "").upper() + ): + self.reporting_amount.setValue(self.invoice_amount.value()) + + def _reload(self) -> None: + rows = self._db.get_invoice_payments(self._invoice_id) + self.table.setRowCount(len(rows)) + invoice_currency = str(self._invoice["currency"] or "") + for idx, row in enumerate(rows): + date_item = QTableWidgetItem(row["received_at"] or "") + date_item.setData(Qt.ItemDataRole.UserRole, int(row["id"])) + self.table.setItem(idx, self.COL_DATE, date_item) + applied = int(row["invoice_amount_cents"] or 0) / 100.0 + reporting = int(row["reporting_amount_cents"] or 0) / 100.0 + report_currency = row["reporting_currency"] or "" + self.table.setItem( + idx, + self.COL_INVOICE_AMOUNT, + QTableWidgetItem(f"{applied:,.2f} {invoice_currency}"), + ) + self.table.setItem( + idx, + self.COL_REPORTING_AMOUNT, + QTableWidgetItem(f"{reporting:,.2f} {report_currency}"), + ) + rate = reporting / applied if applied else 0.0 + self.table.setItem( + idx, self.COL_RATE, QTableWidgetItem(f"{rate:.6f}" if rate else "") + ) + self.table.setItem(idx, self.COL_NOTE, QTableWidgetItem(row["note"] or "")) + + outstanding = self._outstanding_cents() + self.outstanding_label.setText( + strings._("invoice_payment_outstanding").format( + amount=outstanding / 100.0, + currency=invoice_currency, + ) + ) + self.invoice_amount.setMaximum(max(0.0, outstanding / 100.0)) + self.invoice_amount.setValue(outstanding / 100.0) + self._copy_same_currency_amount() + + def _record_payment(self) -> None: + applied_cents = int(round(self.invoice_amount.value() * 100)) + reporting_cents = int(round(self.reporting_amount.value() * 100)) + reporting_currency = self.reporting_currency.text().strip().upper() + if applied_cents <= 0 or reporting_cents <= 0 or not reporting_currency: + QMessageBox.warning( + self, + strings._("error"), + strings._("invoice_payment_amount_required"), + ) + return + try: + self._db.add_invoice_payment( + invoice_id=self._invoice_id, + received_at=self.received_at.date().toString("yyyy-MM-dd"), + invoice_amount_cents=applied_cents, + reporting_currency=reporting_currency, + reporting_amount_cents=reporting_cents, + note=self.note.toPlainText().strip() or None, + ) + except ValueError as exc: + QMessageBox.warning(self, strings._("error"), str(exc)) + return + + self.note.clear() + self.paymentsChanged.emit() + self._invoice = self._db.get_invoice_with_project(self._invoice_id) + self._reload() + + def _delete_payment(self) -> None: + row = self.table.currentRow() + if row < 0: + return + item = self.table.item(row, self.COL_DATE) + if item is None: + return + payment_id = item.data(Qt.ItemDataRole.UserRole) + if payment_id is None: + return + if ( + QMessageBox.question( + self, + strings._("delete"), + strings._("invoice_payment_delete_confirm"), + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, + QMessageBox.StandardButton.No, + ) + != QMessageBox.StandardButton.Yes + ): + return + self._db.delete_invoice_payment(int(payment_id)) + self.paymentsChanged.emit() + self._invoice = self._db.get_invoice_with_project(self._invoice_id) + self._reload() + + +class EarningsReportDialog(QDialog): + """Earnings report using either invoice-date or payment-date recognition.""" + + COL_MONTH = 0 + COL_NET = 1 + COL_TAX = 2 + COL_GROSS = 3 + COL_COUNT = 4 + + def __init__(self, db: DBManager, parent=None): + super().__init__(parent) + self._db = db + self.cfg = load_db_config() + self._detail_rows = [] + self._monthly_rows: list[MonthlyEarnings] = [] + + self.setWindowTitle(strings._("earnings_report")) + self.resize(1040, 740) + root = QVBoxLayout(self) + + form = QFormLayout() + self.reporting_currency = QLineEdit(self.cfg.reporting_currency or "AUD") + self.reporting_currency.setMaxLength(8) + form.addRow(strings._("reporting_currency") + ":", self.reporting_currency) + + self.basis_combo = QComboBox() + self.basis_combo.addItem(strings._("earnings_basis_invoice"), "invoice") + self.basis_combo.addItem(strings._("earnings_basis_payment"), "payment") + form.addRow(strings._("earnings_basis") + ":", self.basis_combo) + + today = QDate.currentDate() + quarter_start_month = ((today.month() - 1) // 3) * 3 + 1 + quarter_start = QDate(today.year(), quarter_start_month, 1) + self.from_date = QDateEdit(quarter_start) + self.from_date.setCalendarPopup(True) + self.from_date.setDisplayFormat("yyyy-MM-dd") + self.to_date = QDateEdit(today) + self.to_date.setCalendarPopup(True) + self.to_date.setDisplayFormat("yyyy-MM-dd") + + self.range_preset = QComboBox() + self.range_preset.addItem(strings._("earnings_this_quarter"), "this_quarter") + self.range_preset.addItem( + strings._("earnings_previous_quarter"), "previous_quarter" + ) + self.range_preset.addItem(strings._("this_year"), "this_year") + self.range_preset.addItem(strings._("custom_range"), "custom") + self.range_preset.currentIndexChanged.connect(self._on_preset_changed) + range_row = QHBoxLayout() + range_row.addWidget(self.range_preset) + range_row.addWidget(self.from_date) + range_row.addWidget(QLabel("—")) + range_row.addWidget(self.to_date) + form.addRow(strings._("date_range") + ":", range_row) + root.addLayout(form) + + self.help_label = QLabel("") + self.help_label.setWordWrap(True) + root.addWidget(self.help_label) + + run_row = QHBoxLayout() + run_row.addStretch(1) + run_btn = QPushButton(strings._("run_report")) + run_btn.clicked.connect(self._run_report) + run_row.addWidget(run_btn) + export_btn = QPushButton(strings._("export_csv")) + export_btn.clicked.connect(self._export_csv) + run_row.addWidget(export_btn) + root.addLayout(run_row) + + self.chart = EarningsChart() + root.addWidget(self.chart) + + self.summary_label = QLabel("") + self.summary_label.setWordWrap(True) + root.addWidget(self.summary_label) + + self.warning_label = QLabel("") + self.warning_label.setWordWrap(True) + root.addWidget(self.warning_label) + + self.table = QTableWidget() + self.table.setColumnCount(5) + self.table.setHorizontalHeaderLabels( + [ + strings._("earnings_month"), + strings._("earnings_sales_ex_tax"), + strings._("earnings_tax"), + strings._("earnings_sales_inc_tax"), + strings._("earnings_invoices"), + ] + ) + header = self.table.horizontalHeader() + header.setSectionResizeMode(self.COL_MONTH, QHeaderView.ResizeMode.Stretch) + for col in (self.COL_NET, self.COL_TAX, self.COL_GROSS, self.COL_COUNT): + header.setSectionResizeMode(col, QHeaderView.ResizeMode.ResizeToContents) + self.table.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers) + root.addWidget(self.table, 1) + + self.details = QTableWidget() + self.details.setColumnCount(9) + self.details.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers) + root.addWidget(self.details, 1) + + close_row = QHBoxLayout() + close_row.addStretch(1) + close_btn = QPushButton(strings._("close")) + close_btn.clicked.connect(self.accept) + close_row.addWidget(close_btn) + root.addLayout(close_row) + + self.basis_combo.currentIndexChanged.connect(self._run_report) + self._run_report() + + def _on_preset_changed(self, _index: int) -> None: + preset = self.range_preset.currentData() + today = QDate.currentDate() + qstart_month = ((today.month() - 1) // 3) * 3 + 1 + qstart = QDate(today.year(), qstart_month, 1) + if preset == "this_quarter": + start, end = qstart, today + elif preset == "previous_quarter": + prev_end = qstart.addDays(-1) + prev_start_month = ((prev_end.month() - 1) // 3) * 3 + 1 + start = QDate(prev_end.year(), prev_start_month, 1) + end = prev_end + elif preset == "this_year": + start, end = QDate(today.year(), 1, 1), today + else: + return + self.from_date.setDate(start) + self.to_date.setDate(end) + + @staticmethod + def _tax_from_reporting_gross(row, gross: int) -> int: + invoice_total = int(row["total_cents"] or 0) + invoice_tax = int(row["tax_cents"] or 0) + return int(round(gross * invoice_tax / invoice_total)) if invoice_total else 0 + + def _configure_headers(self, basis: str) -> None: + if basis == "invoice": + self.help_label.setText(strings._("earnings_report_help_invoice")) + self.table.horizontalHeaderItem(self.COL_COUNT).setText( + strings._("earnings_invoices") + ) + labels = [ + strings._("invoice_issue_date"), + strings._("invoice_client_company"), + strings._("project"), + strings._("invoice_number"), + strings._("invoice_currency"), + strings._("invoice_total"), + strings._("invoice_reporting_total"), + strings._("earnings_tax"), + strings._("invoice_reporting_note"), + ] + else: + self.help_label.setText(strings._("earnings_report_help_payment")) + self.table.horizontalHeaderItem(self.COL_COUNT).setText( + strings._("earnings_payments") + ) + labels = [ + strings._("invoice_payment_received_on"), + strings._("invoice_client_company"), + strings._("project"), + strings._("invoice_number"), + strings._("invoice_currency"), + strings._("invoice_payment_applied_amount"), + strings._("invoice_payment_reporting_amount"), + strings._("earnings_tax"), + strings._("invoice_payment_note"), + ] + self.details.setHorizontalHeaderLabels(labels) + dheader = self.details.horizontalHeader() + for col in range(8): + dheader.setSectionResizeMode(col, QHeaderView.ResizeMode.ResizeToContents) + dheader.setSectionResizeMode(8, QHeaderView.ResizeMode.Stretch) + + def _run_report(self, _index: int | None = None) -> None: + start = self.from_date.date().toString("yyyy-MM-dd") + end = self.to_date.date().toString("yyyy-MM-dd") + if end < start: + QMessageBox.warning( + self, strings._("error"), strings._("earnings_invalid_range") + ) + return + currency = self.reporting_currency.text().strip().upper() + if not currency: + QMessageBox.warning( + self, strings._("error"), strings._("earnings_currency_required") + ) + return + self.reporting_currency.setText(currency) + + basis = str(self.basis_combo.currentData() or "invoice") + self._configure_headers(basis) + warnings: list[str] = [] + + if basis == "invoice": + all_rows = self._db.get_invoices_for_earnings_range(start, end) + self._detail_rows = [ + row + for row in all_rows + if invoice_reporting_amount_cents(row, currency) is not None + ] + missing = len(all_rows) - len(self._detail_rows) + self._monthly_rows = aggregate_invoices_by_month( + self._detail_rows, currency, start, end + ) + if missing: + warnings.append( + strings._("earnings_missing_invoice_values").format( + count=missing, currency=currency + ) + ) + else: + all_rows = self._db.get_payments_for_range(start, end) + self._detail_rows = [ + row + for row in all_rows + if str(row["reporting_currency"] or "").upper() == currency + ] + skipped_currency = len(all_rows) - len(self._detail_rows) + self._monthly_rows = aggregate_payments_by_month( + self._detail_rows, start, end + ) + legacy = self._db.get_paid_invoices_without_payments(start, end) + if legacy: + warnings.append( + strings._("earnings_unstructured_warning").format(count=len(legacy)) + ) + if skipped_currency: + warnings.append( + strings._("earnings_other_currency_warning").format( + count=skipped_currency, currency=currency + ) + ) + + self.table.setRowCount(len(self._monthly_rows)) + for idx, row in enumerate(self._monthly_rows): + self.table.setItem(idx, self.COL_MONTH, QTableWidgetItem(row.month)) + self.table.setItem( + idx, + self.COL_NET, + QTableWidgetItem(f"{row.sales_ex_tax_cents / 100.0:,.2f} {currency}"), + ) + self.table.setItem( + idx, + self.COL_TAX, + QTableWidgetItem(f"{row.tax_cents / 100.0:,.2f} {currency}"), + ) + self.table.setItem( + idx, + self.COL_GROSS, + QTableWidgetItem(f"{row.sales_inc_tax_cents / 100.0:,.2f} {currency}"), + ) + self.table.setItem( + idx, self.COL_COUNT, QTableWidgetItem(str(row.entry_count)) + ) + + total_net = sum(row.sales_ex_tax_cents for row in self._monthly_rows) + total_tax = sum(row.tax_cents for row in self._monthly_rows) + total_gross = sum(row.sales_inc_tax_cents for row in self._monthly_rows) + self.summary_label.setText( + strings._("earnings_totals").format( + ex_tax=f"{total_net / 100.0:,.2f}", + tax=f"{total_tax / 100.0:,.2f}", + inc_tax=f"{total_gross / 100.0:,.2f}", + currency=currency, + ) + ) + self.chart.set_rows(self._monthly_rows) + self.warning_label.setText(" ".join(warnings)) + + self.details.setRowCount(len(self._detail_rows)) + for idx, row in enumerate(self._detail_rows): + if basis == "invoice": + gross = invoice_reporting_amount_cents(row, currency) + if gross is None: + raise RuntimeError( + "Invoice included in earnings report without a " + f"reporting amount: {row['invoice_number']!r}" + ) + + tax = self._tax_from_reporting_gross(row, gross) + values = [ + row["issue_date"] or "", + row["client_company"] or "", + row["project_name"] or "", + row["invoice_number"] or "", + row["currency"] or "", + f"{int(row['total_cents'] or 0) / 100.0:,.2f} {row['currency'] or ''}", + f"{gross / 100.0:,.2f} {currency}", + f"{tax / 100.0:,.2f} {currency}", + row["reporting_note"] or "", + ] + else: + gross = int(row["reporting_amount_cents"] or 0) + tax = self._tax_from_reporting_gross(row, gross) + values = [ + row["received_at"] or "", + row["client_company"] or "", + row["project_name"] or "", + row["invoice_number"] or "", + row["currency"] or "", + f"{int(row['invoice_amount_cents'] or 0) / 100.0:,.2f} {row['currency'] or ''}", + f"{gross / 100.0:,.2f} {currency}", + f"{tax / 100.0:,.2f} {currency}", + row["note"] or "", + ] + for col, value in enumerate(values): + self.details.setItem(idx, col, QTableWidgetItem(str(value))) + + def _export_csv(self) -> None: + if not self._monthly_rows: + QMessageBox.information( + self, strings._("earnings_report"), strings._("earnings_no_data") + ) + return + filename, _ = QFileDialog.getSaveFileName( + self, + strings._("export_csv"), + "earnings.csv", + "CSV Files (*.csv);;All Files (*)", + ) + if not filename: + return + path = Path(filename) + if path.suffix.lower() != ".csv": + path = path.with_suffix(".csv") + currency = self.reporting_currency.text().strip().upper() + basis = str(self.basis_combo.currentData() or "invoice") + count_label = ( + strings._("earnings_invoices") + if basis == "invoice" + else strings._("earnings_payments") + ) + with path.open("w", newline="", encoding="utf-8") as fh: + writer = csv.writer(fh) + writer.writerow( + [ + strings._("earnings_month"), + strings._("earnings_sales_ex_tax"), + strings._("earnings_tax"), + strings._("earnings_sales_inc_tax"), + count_label, + strings._("reporting_currency"), + strings._("earnings_basis"), + ] + ) + for row in self._monthly_rows: + writer.writerow( + [ + row.month, + f"{row.sales_ex_tax_cents / 100.0:.2f}", + f"{row.tax_cents / 100.0:.2f}", + f"{row.sales_inc_tax_cents / 100.0:.2f}", + row.entry_count, + currency, + basis, + ] + ) diff --git a/bouquin/invoices.py b/bouquin/invoices.py index 45cfe59..d1f8947 100644 --- a/bouquin/invoices.py +++ b/bouquin/invoices.py @@ -1071,6 +1071,18 @@ class InvoicesDialog(QDialog): delete_btn.clicked.connect(self._on_delete_clicked) btn_row.addWidget(delete_btn) + reporting_value_btn = QPushButton(strings._("invoice_reporting_value")) + reporting_value_btn.clicked.connect(self._on_reporting_value_clicked) + btn_row.addWidget(reporting_value_btn) + + payments_btn = QPushButton(strings._("invoice_payments")) + payments_btn.clicked.connect(self._on_payments_clicked) + btn_row.addWidget(payments_btn) + + earnings_btn = QPushButton(strings._("earnings_report")) + earnings_btn.clicked.connect(self._on_earnings_clicked) + btn_row.addWidget(earnings_btn) + close_btn = QPushButton(strings._("close")) close_btn.clicked.connect(self.accept) btn_row.addWidget(close_btn) @@ -1148,14 +1160,21 @@ class InvoicesDialog(QDialog): self.project_combo.blockSignals(True) try: self.project_combo.clear() - for proj_id, name in self._db.list_projects(): + projects = self._db.list_projects() + if projects: + self.project_combo.addItem(strings._("all_projects"), None) + for proj_id, name in projects: self.project_combo.addItem(name, proj_id) finally: self.project_combo.blockSignals(False) def _select_initial_project(self, project_id: int | None) -> None: if project_id is None: - if self.project_combo.count() > 0: + # Keep the historical default of selecting the first real project, + # while still exposing an explicit All projects view. + if self.project_combo.count() > 1: + self.project_combo.setCurrentIndex(1) + elif self.project_combo.count() > 0: self.project_combo.setCurrentIndex(0) return @@ -1163,7 +1182,68 @@ class InvoicesDialog(QDialog): if idx >= 0: self.project_combo.setCurrentIndex(idx) elif self.project_combo.count() > 0: - self.project_combo.setCurrentIndex(0) + self.project_combo.setCurrentIndex( + 1 if self.project_combo.count() > 1 else 0 + ) + + def _selected_invoice(self) -> tuple[int, int] | None: + row = self.table.currentRow() + if row < 0: + sel = self.table.selectionModel().selectedRows() + if sel: + row = sel[0].row() + if row < 0: + return None + item = self.table.item(row, self.COL_NUMBER) + if item is None: + return None + invoice_id = item.data(Qt.ItemDataRole.UserRole) + if invoice_id is None: + return None + return row, int(invoice_id) + + def _on_reporting_value_clicked(self) -> None: + selected = self._selected_invoice() + if selected is None: + QMessageBox.information( + self, + strings._("invoice_reporting_value"), + strings._("invoice_required"), + ) + return + _row, invoice_id = selected + + from .earnings import InvoiceReportingValueDialog + + dlg = InvoiceReportingValueDialog(self._db, invoice_id, self) + dlg.exec() + + def _on_payments_clicked(self) -> None: + selected = self._selected_invoice() + if selected is None: + QMessageBox.information( + self, + strings._("invoice_payments"), + strings._("invoice_required"), + ) + return + row, invoice_id = selected + + from .earnings import PaymentsDialog + + dlg = PaymentsDialog(self._db, invoice_id, self) + dlg.paymentsChanged.connect(self.remindersChanged.emit) + dlg.exec() + + invoice = self._db.get_invoice_with_project(invoice_id) + if invoice is not None and invoice["paid_at"] and self.cfg.reminders: + self._remove_invoice_due_reminder(row, invoice_id) + self._reload_invoices() + + def _on_earnings_clicked(self) -> None: + from .earnings import EarningsReportDialog + + EarningsReportDialog(self._db, self).exec() def _current_project(self) -> int | None: idx = self.project_combo.currentIndex() @@ -1371,6 +1451,15 @@ class InvoicesDialog(QDialog): # ---- Dates: issue, due, paid_at (YYYY-MM-DD) ------------------------ if col in (self.COL_ISSUE_DATE, self.COL_DUE_DATE, self.COL_PAID_AT): + if col == self.COL_PAID_AT and self._db.get_invoice_payments(inv_id): + QMessageBox.information( + self, + strings._("invoice_payments"), + strings._("invoice_paid_managed_by_payments"), + ) + _reset_from_db("paid_at", lambda v: v or "") + return + new_date: QDate | None = None if text: new_date = QDate.fromString(text, "yyyy-MM-dd") diff --git a/bouquin/locales/en.json b/bouquin/locales/en.json index b3cd3f9..4045476 100644 --- a/bouquin/locales/en.json +++ b/bouquin/locales/en.json @@ -486,5 +486,49 @@ "project_changelog_type_invoice": "Invoice", "project_changelog_type_bucket": "Bucket", "summary": "Summary", - "details": "Details" + "details": "Details", + "reporting_currency": "Reporting currency", + "invoice_payments": "Payments…", + "invoice_payments_title": "Payments — {invoice} ({project})", + "invoice_payments_summary": "Invoice total: {total:,.2f} {currency}", + "invoice_payment_received_on": "Received on", + "invoice_payment_applied_amount": "Invoice amount applied", + "invoice_payment_reporting_amount": "Receipt value", + "invoice_payment_reporting_help": "Record the receipt's value in your reporting currency. This is used by Payment date reports and for settlement/FX reconciliation; Invoice date reports use the invoice's separate Reporting value instead.", + "invoice_payment_record": "Record payment", + "invoice_payment_exchange_rate": "Effective rate", + "invoice_payment_outstanding": "Outstanding: {amount:,.2f} {currency}", + "invoice_payment_amount_required": "Both the invoice amount applied and receipt value must be greater than zero, and a reporting currency is required.", + "invoice_payment_delete_confirm": "Delete the selected payment record?", + "earnings_report": "Earnings report…", + "earnings_report_help": "Invoice-date mode groups the full value of each invoice by its issue date. Same-currency invoices use their invoice totals automatically; foreign-currency invoices use the structured Reporting value stored on the invoice. Payment dates and bank receipt values do not affect this view.", + "earnings_this_quarter": "This quarter", + "earnings_previous_quarter": "Previous quarter", + "earnings_month": "Month", + "earnings_sales_ex_tax": "Sales excl. tax", + "earnings_tax": "Tax", + "earnings_sales_inc_tax": "Sales incl. tax", + "earnings_payments": "Payments", + "earnings_totals": "Total sales excl. tax: {ex_tax} {currency} Tax: {tax} {currency} Total sales incl. tax: {inc_tax} {currency}", + "earnings_no_data": "No reportable sales in this period.", + "earnings_invalid_range": "The end date cannot be earlier than the start date.", + "earnings_currency_required": "A reporting currency is required.", + "earnings_unstructured_warning": "{count} invoice(s) are marked paid in this period but have no structured payment record, so they are not included. Open Payments… for those invoices to backfill the received amount.", + "earnings_other_currency_warning": "{count} payment(s) use a reporting currency other than {currency} and are not included.", + "invoice_paid_managed_by_payments": "Paid on is managed automatically once structured payment records exist. Open Payments… to add, correct, or remove receipts.", + "invoice_reporting_value": "Reporting value…", + "invoice_reporting_value_title": "Reporting value — {invoice} ({project})", + "invoice_reporting_value_summary": "Invoice date: {issue_date} Invoice total: {total:,.2f} {currency}", + "invoice_reporting_total": "Invoice-date reporting total", + "invoice_reporting_note": "Rate / source note", + "invoice_reporting_value_help": "For a foreign-currency invoice, record the value of the whole invoice in your reporting currency using the valuation date/rate required by your accounting rules. Invoice-date earnings reports use this value and ignore later receipt values. Same-currency invoices need no manual valuation.", + "invoice_reporting_value_required": "A reporting currency and reporting total greater than zero are required.", + "earnings_basis": "Recognition basis", + "earnings_basis_invoice": "Invoice date", + "earnings_basis_payment": "Payment date", + "earnings_invoices": "Invoices", + "earnings_report_help_invoice": "Invoice-date mode groups the full value of each invoice by its issue date. Same-currency invoices use their invoice totals automatically; foreign-currency invoices use the structured Reporting value stored on the invoice. Payment dates and bank receipt values do not affect this view.", + "earnings_report_help_payment": "Payment-date mode groups structured receipts by the date each payment was received. Use this for cash-style reporting or cash-flow analysis; partial payments can fall into different periods.", + "earnings_missing_invoice_values": "{count} foreign-currency invoice(s) in this period do not have a {currency} invoice-date reporting value and are not included. Select each invoice in Manage Invoices and use Reporting value… to backfill it.", + "invoice_reporting_value_clear": "Clear reporting value" } diff --git a/bouquin/settings.py b/bouquin/settings.py index fde863d..90acf9b 100644 --- a/bouquin/settings.py +++ b/bouquin/settings.py @@ -52,6 +52,7 @@ def load_db_config() -> DBConfig: reminders_webhook_secret = s.value("ui/reminders_webhook_secret", None, type=str) documents = s.value("ui/documents", True, type=bool) invoicing = s.value("ui/invoicing", False, type=bool) + reporting_currency = s.value("ui/reporting_currency", "AUD", type=str) locale = s.value("ui/locale", "en", type=str) font_size = s.value("ui/font_size", 11, type=int) return DBConfig( @@ -68,6 +69,7 @@ def load_db_config() -> DBConfig: reminders_webhook_secret=reminders_webhook_secret, documents=documents, invoicing=invoicing, + reporting_currency=reporting_currency, locale=locale, font_size=font_size, ) @@ -88,5 +90,6 @@ def save_db_config(cfg: DBConfig) -> None: s.setValue("ui/reminders_webhook_secret", str(cfg.reminders_webhook_secret)) s.setValue("ui/documents", str(cfg.documents)) s.setValue("ui/invoicing", str(cfg.invoicing)) + s.setValue("ui/reporting_currency", str(cfg.reporting_currency)) s.setValue("ui/locale", str(cfg.locale)) s.setValue("ui/font_size", str(cfg.font_size)) diff --git a/bouquin/settings_dialog.py b/bouquin/settings_dialog.py index 3e1213c..085c42d 100644 --- a/bouquin/settings_dialog.py +++ b/bouquin/settings_dialog.py @@ -296,6 +296,10 @@ class SettingsDialog(QDialog): self.company_phone_edit = QLineEdit(phone or "") self.company_email_edit = QLineEdit(email or "") self.company_tax_id_edit = QLineEdit(tax_id or "") + self.reporting_currency_edit = QLineEdit( + self.current_settings.reporting_currency or "AUD" + ) + self.reporting_currency_edit.setMaxLength(8) self.company_payment_details_edit = QTextEdit() self.company_payment_details_edit.setPlainText(payment_details or "") @@ -314,6 +318,9 @@ class SettingsDialog(QDialog): invoicing_layout.addRow( strings._("invoice_company_tax_id") + ":", self.company_tax_id_edit ) + invoicing_layout.addRow( + strings._("reporting_currency") + ":", self.reporting_currency_edit + ) invoicing_layout.addRow( strings._("invoice_company_payment_details") + ":", self.company_payment_details_edit, @@ -471,6 +478,9 @@ class SettingsDialog(QDialog): invoicing=( self.invoicing.isChecked() if self.time_log.isChecked() else False ), + reporting_currency=( + self.reporting_currency_edit.text().strip().upper() or "AUD" + ), locale=self.locale_combobox.currentText(), font_size=self.font_size.value(), ) diff --git a/bouquin/time_log.py b/bouquin/time_log.py index eaee522..cf3c61a 100644 --- a/bouquin/time_log.py +++ b/bouquin/time_log.py @@ -1080,6 +1080,9 @@ class TimeReportDialog(QDialog): self.manage_invoices_btn = QPushButton(strings._("manage_invoices")) self.manage_invoices_btn.clicked.connect(self._on_manage_invoices) + self.earnings_btn = QPushButton(strings._("earnings_report")) + self.earnings_btn.clicked.connect(self._on_earnings_report) + # Project self.project_combo = QComboBox() self.project_combo.addItem(strings._("all_projects"), None) @@ -1153,6 +1156,7 @@ class TimeReportDialog(QDialog): if getattr(self._db.cfg, "invoicing", False): run_row.addWidget(self.invoice_btn) run_row.addWidget(self.manage_invoices_btn) + run_row.addWidget(self.earnings_btn) root.addLayout(run_row) # Table @@ -1731,6 +1735,11 @@ class TimeReportDialog(QDialog): dlg.exec() + def _on_earnings_report(self) -> None: + from .earnings import EarningsReportDialog + + EarningsReportDialog(self._db, self).exec() + def _on_create_invoice(self) -> None: idx = self.project_combo.currentIndex() if idx < 0: diff --git a/tests/test_earnings.py b/tests/test_earnings.py new file mode 100644 index 0000000..0f67c8f --- /dev/null +++ b/tests/test_earnings.py @@ -0,0 +1,173 @@ +from bouquin.earnings import ( + aggregate_invoices_by_month, + aggregate_payments_by_month, + invoice_reporting_amount_cents, +) + + +def _invoice( + db, + project_id, + number, + currency="AUD", + tax_rate=10.0, + issue_date="2026-04-01", +): + return db.create_invoice( + project_id=project_id, + invoice_number=number, + issue_date=issue_date, + due_date=issue_date, + currency=currency, + tax_label="GST" if tax_rate else None, + tax_rate_percent=tax_rate, + detail_mode="summary", + line_items=[("Consulting", 10.0, 1000)], + time_log_ids=[], + ) + + +def test_partial_payments_mark_invoice_paid_only_when_fully_settled(fresh_db): + project_id = fresh_db.add_project("Client A") + invoice_id = _invoice(fresh_db, project_id, "INV-1") + + first = fresh_db.add_invoice_payment( + invoice_id, + received_at="2026-04-15", + invoice_amount_cents=5500, + reporting_currency="AUD", + reporting_amount_cents=5500, + note="First half", + ) + assert first > 0 + assert fresh_db.get_invoice_with_project(invoice_id)["paid_at"] is None + + second = fresh_db.add_invoice_payment( + invoice_id, + received_at="2026-05-03", + invoice_amount_cents=5500, + reporting_currency="AUD", + reporting_amount_cents=5500, + note="Balance", + ) + assert second > first + assert fresh_db.get_invoice_with_project(invoice_id)["paid_at"] == "2026-05-03" + + fresh_db.delete_invoice_payment(second) + assert fresh_db.get_invoice_with_project(invoice_id)["paid_at"] is None + + +def test_invoice_basis_uses_invoice_date_value_not_later_bank_receipt(fresh_db): + project_id = fresh_db.add_project("Foreign Client") + invoice_id = _invoice(fresh_db, project_id, "USD-1", currency="USD") + + # Value of the invoice in AUD on the invoice-date conversion basis. + fresh_db.set_invoice_reporting_value( + invoice_id, + reporting_currency="AUD", + reporting_total_cents=16500, + note="Invoice-date FX rate", + ) + + # The eventual bank receipt is later and a different AUD amount. + fresh_db.add_invoice_payment( + invoice_id, + received_at="2026-05-20", + invoice_amount_cents=11000, + reporting_currency="AUD", + reporting_amount_cents=17000, + note="Actual receipt", + ) + + invoice_rows = fresh_db.get_invoices_for_earnings_range("2026-04-01", "2026-06-30") + monthly = aggregate_invoices_by_month( + invoice_rows, "AUD", "2026-04-01", "2026-06-30" + ) + + assert monthly[0].month == "2026-04" + assert monthly[0].sales_inc_tax_cents == 16500 + assert monthly[0].tax_cents == 1500 + assert monthly[0].sales_ex_tax_cents == 15000 + assert monthly[0].entry_count == 1 + assert monthly[1].sales_inc_tax_cents == 0 + + payment_rows = fresh_db.get_payments_for_range("2026-04-01", "2026-06-30") + payment_monthly = aggregate_payments_by_month( + payment_rows, "2026-04-01", "2026-06-30" + ) + assert payment_monthly[0].sales_inc_tax_cents == 0 + assert payment_monthly[1].sales_inc_tax_cents == 17000 + + +def test_same_currency_invoice_needs_no_manual_reporting_value(fresh_db): + project_id = fresh_db.add_project("Local Client") + _invoice(fresh_db, project_id, "AUD-1", currency="AUD") + + rows = fresh_db.get_invoices_for_earnings_range("2026-04-01", "2026-04-30") + assert len(rows) == 1 + assert rows[0]["reporting_total_cents"] is None + assert invoice_reporting_amount_cents(rows[0], "AUD") == 11000 + + monthly = aggregate_invoices_by_month(rows, "AUD", "2026-04-01", "2026-04-30") + assert monthly[0].sales_inc_tax_cents == 11000 + assert monthly[0].tax_cents == 1000 + + +def test_invoices_are_aggregated_by_issue_month(fresh_db): + project_id = fresh_db.add_project("Client B") + _invoice( + fresh_db, + project_id, + "INV-APR", + tax_rate=None, + issue_date="2026-04-30", + ) + _invoice( + fresh_db, + project_id, + "INV-JUN", + tax_rate=None, + issue_date="2026-06-01", + ) + + rows = fresh_db.get_invoices_for_earnings_range("2026-04-01", "2026-06-30") + monthly = aggregate_invoices_by_month(rows, "AUD", "2026-04-01", "2026-06-30") + + assert [r.sales_inc_tax_cents for r in monthly] == [10000, 0, 10000] + assert [r.entry_count for r in monthly] == [1, 0, 1] + + +def test_foreign_invoice_without_reporting_value_is_not_guessed(fresh_db): + project_id = fresh_db.add_project("Foreign Client") + _invoice(fresh_db, project_id, "USD-MISSING", currency="USD") + + rows = fresh_db.get_invoices_for_earnings_range("2026-04-01", "2026-04-30") + assert invoice_reporting_amount_cents(rows[0], "AUD") is None + + monthly = aggregate_invoices_by_month(rows, "AUD", "2026-04-01", "2026-04-30") + assert monthly[0].sales_inc_tax_cents == 0 + assert monthly[0].entry_count == 0 + + +def test_reporting_value_is_invalidated_when_invoice_date_changes(fresh_db): + project_id = fresh_db.add_project("Foreign Client") + invoice_id = _invoice(fresh_db, project_id, "USD-EDIT", currency="USD") + fresh_db.set_invoice_reporting_value(invoice_id, "AUD", 16500, "RBA") + + fresh_db.set_invoice_field_by_id(invoice_id, "issue_date", "2026-04-02") + invoice = fresh_db.get_invoice_with_project(invoice_id) + assert invoice["reporting_currency"] is None + assert invoice["reporting_total_cents"] is None + assert invoice["reporting_note"] is None + + +def test_legacy_paid_invoice_is_reported_as_unstructured_for_payment_basis(fresh_db): + project_id = fresh_db.add_project("Legacy Client") + invoice_id = _invoice(fresh_db, project_id, "LEGACY-1") + fresh_db.set_invoice_field_by_id(invoice_id, "paid_at", "2026-04-22") + fresh_db.set_invoice_field_by_id(invoice_id, "payment_note", "Received AUD 110.00") + + missing = fresh_db.get_paid_invoices_without_payments("2026-04-01", "2026-06-30") + + assert len(missing) == 1 + assert missing[0]["invoice_number"] == "LEGACY-1" diff --git a/tests/test_settings.py b/tests/test_settings.py index 086d590..5874403 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -14,6 +14,7 @@ def _clear_db_settings(): "ui/tags", "ui/time_log", "ui/reminders", + "ui/reporting_currency", "ui/locale", "ui/font_size", ]: @@ -32,6 +33,7 @@ def test_load_and_save_db_config_roundtrip(app, tmp_path): tags=True, time_log=True, reminders=True, + reporting_currency="NZD", locale="en", font_size=11, ) @@ -46,6 +48,7 @@ def test_load_and_save_db_config_roundtrip(app, tmp_path): assert loaded.tags == cfg.tags assert loaded.time_log == cfg.time_log assert loaded.reminders == cfg.reminders + assert loaded.reporting_currency == cfg.reporting_currency assert loaded.locale == cfg.locale assert loaded.font_size == cfg.font_size From e7b5c99b73d08f8743640263cebabe6184f7eb55 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 25 Aug 2026 17:16:55 +1000 Subject: [PATCH 2/3] Dependency updates --- CHANGELOG.md | 1 + debian/changelog | 7 + poetry.lock | 683 ++++++++++++++++++++++++++--------------------- rpm/bouquin.spec | 5 +- 4 files changed, 397 insertions(+), 299 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f88ee4..8459bf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # 0.10.0 * New 'Earnings' interface for viewing/tracking earnings across reporting periods (e.g for BAS) + * Dependency updates (including upgrade to SQLCipher 4.18.0) # 0.9.0 diff --git a/debian/changelog b/debian/changelog index 66c85ee..ac05239 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,10 @@ +bouquin (0.10.0) unstable; urgency=medium + + * New 'Earnings' interface for viewing/tracking earnings across reporting periods (e.g for BAS) + * Dependency updates (including upgrade to SQLCipher 4.18.0) + + -- Miguel Jacq Tue, 25 Aug 2026 17:18:00 +1000 + bouquin (0.9.0) unstable; urgency=medium * Add 'Projects' interface for unified time/invoice/docs view. diff --git a/poetry.lock b/poetry.lock index f3bb0e2..dd8cb40 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,163 +1,209 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.4.1 and should not be changed by hand. [[package]] name = "bouquin-sqlcipher4" -version = "4.16.0" +version = "4.18.0" description = "DB-API 2.0 interface for SQLCipher 4.x, for use with Bouquin" optional = false python-versions = "<4.0,>=3.10" +groups = ["main"] files = [ - {file = "bouquin_sqlcipher4-4.16.0-cp313-cp313-manylinux_2_41_x86_64.whl", hash = "sha256:f5146a160ab7e5c1c3a4b8911e40fa5b45c2c427e0cd271ce593337913f173d3"}, - {file = "bouquin_sqlcipher4-4.16.0.tar.gz", hash = "sha256:30ee40f3173fca27132de2d096c2f93271b0a1c3768aa17e1bb39d416672a0ef"}, + {file = "bouquin_sqlcipher4-4.18.0-cp313-cp313-manylinux_2_41_x86_64.whl", hash = "sha256:6244cb6ecc368fb303f45a9efb8cdf1a62c58d8557397690d6618a2da4dec772"}, + {file = "bouquin_sqlcipher4-4.18.0.tar.gz", hash = "sha256:f9e64ed278cc710dc0fd7a1d5bc0f1048bbf7c6913ba14453b3b13f9ac63edb3"}, ] [[package]] name = "certifi" -version = "2026.5.20" +version = "2026.7.22" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ - {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, - {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, + {file = "certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775"}, + {file = "certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55"}, ] [[package]] name = "charset-normalizer" -version = "3.4.7" +version = "3.5.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ - {file = "charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-win32.whl", hash = "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e5f4d355f0a2b1a31bc3edec6795b46324349c9cb25eed068049e4f472fb4259"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16d971e29578a5e97d7117866d15889a4a07befe0e87e703ed63cd90cb348c01"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dca4bbc466a95ba9c0234ef56d7dd9509f63da22274589ebd4ed7f1f4d4c54e3"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e80c8378d8f3d83cd3164da1ad2df9e37a666cdde7b1cb2298ed0b558064be30"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:36836d6ff945a00b88ba1e4572d721e60b5b8c98c155d465f56ad19d68f23734"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_armv7l.whl", hash = "sha256:bd9b23791fe793e4968dba0c447e12f78e425c59fc0e3b97f6450f4781f3ee60"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aef65cd602a6d0e0ff6f9930fcb1c8fec60dd2cfcb6facaf4bdb0e5873042db0"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:82b271f5137d07749f7bf32f70b17ab6eaabedd297e75dce75081a24f76eb545"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:1efde3cae86c8c273f1eb3b287be7d8499420cf2fe7585c41d370d3e790054a5"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:c593052c465475e64bbfe5dbd81680f64a67fdc752c56d7a0ae205dc8aeefe0f"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:af21eb4409a119e365397b2adbaca4c9ccab56543a65d5dbd9f920d6ac29f686"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:84c018e49c3bf790f9c2771c45e9313a08c2c2a6342b162cd650258b57817706"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dd915403e231e6b1809fe9b6d9fc55cf8fb5e02765ac625d9cd623342a7905d7"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-win32.whl", hash = "sha256:320ade88cfb846b8cd6b4ddf5ee9e80ee0c1f52401f2456b84ae1ae6a1a5f207"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:1dc8b0ea451d6e69735094606991f32867807881400f808a106ee1d963c46a83"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:177a0ba5f0211d488e295aaf82707237e331c24788d8d76c96c5a41594723217"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e0d51f618228538a3e8f46bd246f87a6cd030565e015803691603f55e12afb5"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:14265bfe1f09498b9d8ec91e9ec9fa52775edf90fcbde092b25f4a33d444fea9"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:87fad7d9ba98c86bcb41b2dc8dbb326619be2562af1f8ff50776a39e55721c5a"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f22dec1690b584cea26fade98b2435c132c1b5f68e39f5a0b7627cd7ae31f1dc"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:d61f00a0869d77422d9b2aba989e2d24afa6ffd552af442e0e58de4f35ea6d00"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6370e8686f662e6a3941ee48ed4742317cafbe5707e36406e9df792cdb535776"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a6c5863edfbe888d9eff9c8b8087354e27618d9da76425c119293f11712a6319"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ed065083d0898c9d5b4bbec7b026fd755ff7454e6e8b73a67f8c744b13986e24"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2cd4a60d0e2fb04537162c62bbbb4182f53541fe0ede35cdf270a1c1e723cc42"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:813c0e0132266c08eb87469a642cb30aaff57c5f426255419572aaeceeaa7bf4"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:07d9e39b01743c3717745f4c530a6349eadbfa043c7577eef86c502c15df2c67"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c0f081d69a6e58272819b70288d3221a6ee64b98df852631c80f293514d3b274"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-win32.whl", hash = "sha256:8751d2787c9131302398b11e6c8068053dcb55d5a8964e114b6e196cf16cb366"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:12a6fff75f6bc66711b73a2f0addfc4c8c15a20e805146a02d147a318962c444"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:bb8cc7534f51d9a017b93e3e85b260924f909601c3df002bcdb58ddb4dc41a5c"}, - {file = "charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d"}, - {file = "charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5"}, + {file = "charset_normalizer-3.5.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1ee1e296209fdce05b81b663250eefa02213a2da7b41bf26f7829b8ba3545aa"}, + {file = "charset_normalizer-3.5.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9fbdce1e47394b09bc9f26ab117dfc8d6491977a11d86f592bb42c779db2fda"}, + {file = "charset_normalizer-3.5.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:00668ebb0609751758682eb0b5857e7c35b9f00e84dfdef062e103244ec94d45"}, + {file = "charset_normalizer-3.5.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba2f37ee79e6338845261a3c5b1784e5d1acdff2c0785b284f1b633033d136ab"}, + {file = "charset_normalizer-3.5.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ce854f5f478050ade5a238731c4ca985a7d3b3cb53ff600a9b5c3b689b5f0a7a"}, + {file = "charset_normalizer-3.5.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:96eefc178f8636b9c760c5829345307fd81cfae9ab1e80997dbddeb0f54ee9a3"}, + {file = "charset_normalizer-3.5.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:366ec70f5547c640d3ce1985722490f23faf4eb5216a7eeba78277490e78dacb"}, + {file = "charset_normalizer-3.5.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:950f23cb393f85543777b0433f082cddd25b51ab398eac7971146495679efe5f"}, + {file = "charset_normalizer-3.5.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c1dcc36dcb96abc02236e182d17e0f71430152a6c2c7447421da2d2dc144edea"}, + {file = "charset_normalizer-3.5.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:07ffd07412fc5d5e84cd8952acf9ff7e4ed7a708e69d1bada19d8ba91711353f"}, + {file = "charset_normalizer-3.5.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:f5542f9b941279d82d41eb0aa9f98eba36fe4df5c7086c651df7944935b37182"}, + {file = "charset_normalizer-3.5.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a545775cfe815855ea32d7c27731d79da358ef2055b4a25830231b1622dd18aa"}, + {file = "charset_normalizer-3.5.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:494b70049a4d69aec6e8137c13af4cf8db8c9f9820a1392ac293b0dd2987a818"}, + {file = "charset_normalizer-3.5.1-cp310-cp310-win32.whl", hash = "sha256:94fbf1c0c6cc0d3d5e50f9a9313a8cdca90dd696d34b381cd1704f8c9e939f20"}, + {file = "charset_normalizer-3.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:be47f99644b208bff7766314013f9acf57b056b04191d570d68ad14022cf5b1d"}, + {file = "charset_normalizer-3.5.1-cp310-cp310-win_arm64.whl", hash = "sha256:a6d095662e73e74f0a49988e0593373e243e3a52e27bfeea0a859e88acf4a0f5"}, + {file = "charset_normalizer-3.5.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eda059b6bc8bc0812d626fd91a7ce01bf583df0a61296eff390fd94141a34e30"}, + {file = "charset_normalizer-3.5.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa2bb0b37202dca27175591f761108b5d34096ade1191ffe4808bdf6b1571488"}, + {file = "charset_normalizer-3.5.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b2b1b3fa5670c127b246df1d0c059defd41f689a868a3b9d79df9b1cac42d22"}, + {file = "charset_normalizer-3.5.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6e5e4d73d588ca5ed09df1b7dcd1b203d1df3c542e3f50d126c947d432b10731"}, + {file = "charset_normalizer-3.5.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b54e7e13267d49ffbfe68e25b3cbd774dab38fa37238f71265e91b36146eb21c"}, + {file = "charset_normalizer-3.5.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7b742bf31c88566b4bb6335a7f393bb322e580b6bb98df7bd0c25e6e3519ce8"}, + {file = "charset_normalizer-3.5.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6ba32c4d2abf1d2fe7cf27d280f4cca5664233b0f885549c7761719eb977f486"}, + {file = "charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0722590aabf9dc6a6c0343d523c05458fa2b5047dbe6302fd526bb570600753f"}, + {file = "charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:aa1099b956fb795e686d073568f6dc002a0bb89765ea6d5b055dd7d9bf1b116c"}, + {file = "charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bd6c173f04743d483881bffa1478d5a4624475b8cd1d2194956a75548e191c18"}, + {file = "charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:f298e218441525d3794428b4c8b8fb8662c6d3ea79925d4807ee6b9a96a3bca5"}, + {file = "charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:6e2912d4babbc65196ac13c2f53468dc57fb8b9c25ef913e8c59ddf7c6dc0e1b"}, + {file = "charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3d27167433c0d5f18dc850f07d0b3816221984fecdc405d6c157a6f0b8f8e9e6"}, + {file = "charset_normalizer-3.5.1-cp311-cp311-win32.whl", hash = "sha256:ac00177c4831ffa650f8609e4bdddd5fe09c03b1c0c47acece7e6ea20421598b"}, + {file = "charset_normalizer-3.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:f9b1e28d0e8dbfa858abdba91d6b547beaf2df1a59bec6da6faae7b96a4991a9"}, + {file = "charset_normalizer-3.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:ae31a1a1db2ee6cc2942fccaf695c934bc7f3db9f2133a3fef1f367cf1a4ab10"}, + {file = "charset_normalizer-3.5.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5b6d1386bf0096d26d3a863dc0a487a5b4eb9aa93cf5ba69683d29dde6b9d60f"}, + {file = "charset_normalizer-3.5.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4582c27e8c889d64811987b5967fbd3ae0c823fe1fd933b543d55ac20bb475fa"}, + {file = "charset_normalizer-3.5.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1d1c7a53a6c2103925cdd6d7229f8c567379f211c869793df679f2e9f738c369"}, + {file = "charset_normalizer-3.5.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e6621fb2a4988d6e53eedc455e5903e2679f3967b8acb3d639f1b63c14a2e893"}, + {file = "charset_normalizer-3.5.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7c0c10730342b0c9b35dd1d619beb8214e520bd96a1f870f452680b238aab3e0"}, + {file = "charset_normalizer-3.5.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b9af956078716df40d985fb0dfeb2c2120c5ca92ba4ff4b388acfd01cdc14d08"}, + {file = "charset_normalizer-3.5.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f9f8405c2c758532c74fed975dbee57be1f31a6e865c031870c79a6ed3212ada"}, + {file = "charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:96fef3e886d6a9874b14f27fc193fbdc69d5d8035783d86aa4e1cea594e695f9"}, + {file = "charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5d8531a6569d025f68e2321e7638fb7978f23db58e5f69f56913837aae03816e"}, + {file = "charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:aae2ee51122d3ae968a3837d97dc24a0aeebb0dea23694422cd172bd30017cd6"}, + {file = "charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7235dc28fc6dd9d832ac7c7bce95367dedb85929f17368a0c2bee1e080b9acbf"}, + {file = "charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4abdc5f9ad448c1ecbfae2974b820535d6bc6e7eef63babbab3d81cf46968c71"}, + {file = "charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ba501e667c17d8411f98e67a022d9604ef179aff0e459b7e292c796837c13573"}, + {file = "charset_normalizer-3.5.1-cp312-cp312-win32.whl", hash = "sha256:cfa1c0cc3a8f9f53f1243a5a99ac36fd003880199383b37672e86ddda9cb07e2"}, + {file = "charset_normalizer-3.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:3617ac3cfd8b9888f145ad89dd6e692285834b0201c6074a5eeaad3fd4d668c2"}, + {file = "charset_normalizer-3.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:88e85ab89cb822c1e635f51d6d32e488f94e002e70e2f492bdb8b945543f345a"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:4f298bdadb8f0b9e5672877f647d1be9373ef5320c9e2f049795e26cad28b6a9"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-android_24_x86_64.whl", hash = "sha256:88ca277405c2d3b71c4e1c2ee0e7966e807bcba86a69d11e19ba199d18ae4491"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9362dd90aa7dab48c0054a21187791ccf05473f7dba5d92b8033ae62164675e7"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:977cdbd483a9cff38179bea4fd754289a6f2195c7abd414aba85410b3e66cc5e"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e90251c0c7bdd54a100a0dce3c07b7e637278c93af29dbf78ebb89a58c4bac7d"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94d78ecec2605a8d0398b0f365d5f12a63248438516f5dac536a5eff7337df4a"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d59b75732e9b6f27388e10c14b0259cc5f2e48c78627d185e6a177b58ad3cffe"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0d929fc574b4d6fd9e7c0f5c2ede8716a41911923aa7fa5fce38e0818aa4a1ac"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:394fea06235c8543390050ed5f529187074b029fb027213f6c46ac11ab5d950e"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62b55f6722735a6c472f88361cde6640608773d9443cebdbb51abf436a1fcdd3"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa48b1b63d639f9483e0633e092f5851e2348c352f1f9bb6c8182f87884ef876"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c71fb0d56c920c269cd3e2e3fe7c610e3f1fdb21a6ce60efa6430ff63676cea6"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:485a0d363cafefcd2538a73c7c838daa2035f09b2c9f9b5e3133f80c6aeb84c2"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c0ea61a470e070686aa30892fed79e297d2c8d0ab46b8bcdf027d38c51da591"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:90b7481fb62fbe172c558bc6fd1c4c98d82004a54a7551f20e11ac9bf0b8708c"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:35fe081843b35aad20ffeccec3eeffbe637b15d14f3fb22cc1b59cd8ec17e93c"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fd0350afdc3aabd5576f60ea109228bd5538139713c7b094c5cd27c73a98bc6f"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:9d9a0dc7cbe9bec24c3f767c9122c41fe5a1bc43f47cd099d00d393e09769de4"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-win32.whl", hash = "sha256:d63600d620ad0064c3a748b950ac5ea38a80190e5498532efefa4b7b3f1da1f3"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:aea996a6aba25260827c9ea511d1addfde2da9eb686ac961838509086188b7e6"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:fd0a274c0e5f9a21565cd9d3dd749b61f96b7aa1e20a93aa1ba4029518f2e5c0"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:774d157f112367ff4abd29019f38f023c24e00e56edc7829c20e358a5a913ad8"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:26422d45fd13551cf564c58932f7d72b4f58b93b0fcf18c35ba6be12b46bb102"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:09a7bba9f739468c8e78c36a75c33768e53cb1959fc638f510454c14683f00d5"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4c9548dc78002099910abaebc0a72ac58b7d30931869e0351c09b507dff4ece3"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c428c6c31eb5f4277d7f8eccaf767fbd548ddd5ce3c8b4f4cbbfab3d96b5904c"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2f06b7eae9dbe77fe1d644ca244dad508de8d302870a43f3c559b521270938a0"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b7430cf5728e68f6c462254009a6ef4086e1bea43cf2f57aa9c55fb4f50ff96"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab743e9bc90c1f73552ec33e10e3331315acd2c397b36065b591b0181de533cc"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6f7deae3feb4edfa2efaf7c574fe88cbf055038a6abdb40188e4fff66d5699f"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15f024313246a4ed976c60f440bb8d257815513a681d212ff74fd46f7d715a90"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:823f82903d189af463d7df250ef1f7f696f3cee08cc8d91deb565e8d425f6506"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:01e93745f7f219b703b60ba7afead36cfc4242782be5af484673fc500df12da5"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:329fc3ccb63ad22d867d84c2adea759a64079a37ba4a343433b02c7a2816871e"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:bb57753e36e4855b8ca375069482250a6246372331a3e4f3407eaebb007443f5"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:fce8cbd4997efeb450bd298b54f755dcdff18d496f7a5ddbb4867c6d7c88fdc3"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:6c9cdde8becb25a7fde49924511aa2644d6f8081cc8df8e9452724303348d8e3"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9ac4444d8d4fd4c4bd08bf451ed3167aa9e7ec6cdb41b648794f1d1103652e36"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:f03ac127268b43ef4fe9e6ab6794a6794b49485a0cc0c1db79876d2f33f75bc7"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-win32.whl", hash = "sha256:1f5883d77fd409a261abb5dc8ccbe335720d798b1de4abb3b1d47ccbbc76b53b"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:c658c50ac0c98cd755a2dd50b7977d3bca7df401dcc47fbdfa87db53ef7d4e8b"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:4bea7f8ebe90bbd7f0e4a2de42ca6924ba23e3e76418c408ff82f1d46fabd687"}, + {file = "charset_normalizer-3.5.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:fbc597639158fd7c14d55e808718848319540f51b0e6746e3eefa59723a4a348"}, + {file = "charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e71c909f353863b2b89c83de2ebed71ea6d0df8a6ef65a128193c5e650766bef"}, + {file = "charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7ac76cf9afd34929d76eb7fcb63be476a4853d8a96f0dcf2d0db68a0cbdf9885"}, + {file = "charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a3a370082ce34d0612f421e15fe011c53bb1feff21a26d06ad4fb244dab5a375"}, + {file = "charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:256dd4d85d9e4dc595e2bc983c980e73f62ddeb3165c58b4c3dfe78c5c8548c1"}, + {file = "charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:58d4aa13a59c969dbfdf9e6a9560e242cbfd9e8a8f50c2747714df1a423adf65"}, + {file = "charset_normalizer-3.5.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0c6dfb5ca6723eeed15aa8e564a014d69fcb8812f94eef11fe3631e0508199f5"}, + {file = "charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c010f5581d9c612804cc59fcf7b524b707fbcb72828551237ab545bb5c7034af"}, + {file = "charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:52ec005752a56ae79547a05c0139ca2501a0c866390b6115008456b9f0e7cde1"}, + {file = "charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2bced4061f000f7187254a02ad3433ae17eaf991747ceea2f478422590a5bba9"}, + {file = "charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:9eea3ab2597a5e65fe65296e2d6a84570845a6b55532d90333d740d48bbc850a"}, + {file = "charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:496846868fea80e479324862fa877f02411f2fd0f83b79ccee2607aa68b2a032"}, + {file = "charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:85d5855daafc240cc045c026d7a15fd198a09b0fc8ff6f5ecbb5297b509cb11e"}, + {file = "charset_normalizer-3.5.1-cp314-cp314t-win32.whl", hash = "sha256:58d3e12c88e0950bca850ae1f7c256055c097639c2edb9eb123af9807d8b15e4"}, + {file = "charset_normalizer-3.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:acaf604462bf330b0d07e7a07c1d6e4adac79e5fb13e9c5140590542cafacc00"}, + {file = "charset_normalizer-3.5.1-cp314-cp314t-win_arm64.whl", hash = "sha256:fdb8a068947befafba9952162645dc2fecaeb400e64584829ed5e9b2fbe21a7f"}, + {file = "charset_normalizer-3.5.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:9085f87b0e38a2b92b8923059b4e8789fe40d9279712d15dcc670048d77079af"}, + {file = "charset_normalizer-3.5.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2679de311c7946dde5d3b6f44941844133ff5c7cb86099c0061ab1e8901c20a8"}, + {file = "charset_normalizer-3.5.1-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:baf3775a2635e5a11fbd5e4e64ee69c7e86875d224a5c72aca4c141064589a90"}, + {file = "charset_normalizer-3.5.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ac8c94b6539074e0f40899301273ac8402b9b3e01c7b7ba269ff30340aaaf20"}, + {file = "charset_normalizer-3.5.1-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fe532b3c966d1fb794e0698e4589d0444017ae77fc0b31edea13c0e35bcc449"}, + {file = "charset_normalizer-3.5.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5c84bec0ab5ae0c64bfe73a7d2adcb5ce73b467523fc27fd6a28ab2aa6cbe35a"}, + {file = "charset_normalizer-3.5.1-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:854066be00447fa8de2ccbbe893e2ffc4b123ef16d897af794c1e18bd4a714b0"}, + {file = "charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:21b82d8082f6f5e7f456ef0bd16323d08de1266efbfeb476e64b2a91d1471a4e"}, + {file = "charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:838648accb3a7fd9803fd45c87bce8509648eb0c11bc34e216141300977244f2"}, + {file = "charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:195ce897c6153c0700078142cf8efe3e6454ca4cf4357499e4078dfd83396626"}, + {file = "charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:978eab16f55b4ab2c2a745be9a0a840bf8f09a7f227d9c76eb30214d078865a5"}, + {file = "charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:cc0329df4caaceb950d2f580b5ac716a377f7059624a0bafaeaf8a218c6ed774"}, + {file = "charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:687c9ca3035544b113bea2055e180af96fb63c0c476e22a9180f51925186e7b7"}, + {file = "charset_normalizer-3.5.1-cp315-cp315-win32.whl", hash = "sha256:706bfd38730a5ac7a365793269a00f4e988178cec121391f4248d84ad8c972e9"}, + {file = "charset_normalizer-3.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:92caef967d287a407085d61176fce4012b1dd62daed4eb6d5ceb26d3d2538712"}, + {file = "charset_normalizer-3.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:5fc45d653ea8c9a20479167e11d4a0f8cb2fa3470737ab6f9c827532313187b7"}, + {file = "charset_normalizer-3.5.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:59171c6e45bf07d0d5cab3b0bf81d945035530f6873398b3b531c31184d46663"}, + {file = "charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9dbdd9205662134957cf0c324f639bdc5031c0ca056e2369e238db75187c0f11"}, + {file = "charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e4b018dc5a0eee4676e38fe84a47a427816c590b93b55d9025274ec4d6ffc2dc"}, + {file = "charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ced3fdd71aaa83ce593746c2edb42b7a59cb4c19c8b5c407781c72e493aae55a"}, + {file = "charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:19a3dd5aa73cef1c99687c4fc57db016a9c17104ae1185da88ba566a5d3bebe4"}, + {file = "charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cc5d36d96478aa9c60654bd932525bf32964c62a7281eafdf16d85003a8d6004"}, + {file = "charset_normalizer-3.5.1-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:04368edf83514385ffc3e1cfd4546e595f4f1272dd23ba437a93a9cc3741d47b"}, + {file = "charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:9b5db6052055d34d41230fb78d7c439c23dc536a9896f6cb039e8dd92cfc1263"}, + {file = "charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:252d099029bcbea642f2a06c4ed5046bdf8b5a8150b64afa5e027e88b106e5ee"}, + {file = "charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:6199d5606e2bbf2b096cf64d03f8b6790c91081d5ac866b8e7bb6422738cc60c"}, + {file = "charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:77efcff2b23071c349402ac1066667a3d011f62398d81408c9b88ad991747c9e"}, + {file = "charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:a5cbd90ecf0fc62e64726917ad083b73001f0563657a87ec3c0b504e277dc90d"}, + {file = "charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:4d26f14f041e83dd8edfd61f4cd4fa7285d31798b5bf1f28e70c367ba6c41d61"}, + {file = "charset_normalizer-3.5.1-cp315-cp315t-win32.whl", hash = "sha256:ac13b004224fb341e1e25a1ed5e19d32f57cdb2a403e01f003b46f051a550f6f"}, + {file = "charset_normalizer-3.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:35aea775dc2bd5f54cd84a1cd2696cc3207c479cb9cf0bd346f0d343e4300ddb"}, + {file = "charset_normalizer-3.5.1-cp315-cp315t-win_arm64.whl", hash = "sha256:fb78f6e7fcd8ad785d28cd577168bc1aaee827b25bb8755638f694794ea98f0a"}, + {file = "charset_normalizer-3.5.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:41876ee62a3dddf48ff1121ad8f0798032aa03f2fd35f21f34a4cab14f18d8d2"}, + {file = "charset_normalizer-3.5.1-cp37-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6dac12ff6b846103483683f60c5f8fee205121adc58ffd87e90a90a3af69e99"}, + {file = "charset_normalizer-3.5.1-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cee5dd7c6fb5dd52a0fe2a740f9bc6e3593f5f8b1788bde49de02086f30182b2"}, + {file = "charset_normalizer-3.5.1-cp37-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:343fb4f2821043bd87095f7b08a1a181febc8e36ac64212143bbfd0a0e1bc235"}, + {file = "charset_normalizer-3.5.1-cp37-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ae4a097991662cd4fff0ddc74e0fe7874f82e00042fa0ea00855645ed0c79598"}, + {file = "charset_normalizer-3.5.1-cp37-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b599739b93b2cbeded49645ae3c8d1405c29ddfbceac1545c87a3f9580a9e96"}, + {file = "charset_normalizer-3.5.1-cp37-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b39b69b347e5e47a3b5b8cfc005c68c1ba347474e3960236c4944a8ecd174962"}, + {file = "charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a2028475ba855475b8b4d3cfeb4994269c967aea8b9892dfba907f4263a863a3"}, + {file = "charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:36047af20e17097c3bb9476c2b7655f2f7aa51322c0ba58c07695bedf755a950"}, + {file = "charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4c4fb141a727957c93edfe5c32a26ceb6b5f6461d67146e2d39f51e16170bea8"}, + {file = "charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:2f293479cce755c75f1697e87c409b7ae4c555c7dfecb6e988ad13abba943031"}, + {file = "charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_s390x.whl", hash = "sha256:3588e376b3ea2eea84976f67273d679f229e24c66dce7b82ae45aef04ff6e072"}, + {file = "charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e199fb99720074809a7720f1c0b4d919eea8b87e88713e0f8f602f7bef543d9d"}, + {file = "charset_normalizer-3.5.1-cp37-abi3-win32.whl", hash = "sha256:dd732602a7009217f658d5863d12d79d373a4de0eebc111094bcdd3bb8e0a6cc"}, + {file = "charset_normalizer-3.5.1-cp37-abi3-win_amd64.whl", hash = "sha256:70055ff39b97c99e7ae40ea3e393fb62aa2e44dbd9b29f8d14f42fb0025c3959"}, + {file = "charset_normalizer-3.5.1-cp37-abi3-win_arm64.whl", hash = "sha256:87e4f41d375c0b9be2fb5251aee4b8a689169e134535aed81bf085c3b647451e"}, + {file = "charset_normalizer-3.5.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:85de3134b5379856e323ba37c19c9256d39425f7b76a63af52b09fb4664c2e8f"}, + {file = "charset_normalizer-3.5.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3e5e1224c0a6a90e05843e07adfec669edebec17801c67072f51e59561d63c0b"}, + {file = "charset_normalizer-3.5.1-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5e2d0e146dcb57034f8b97dc58d2d512cb90aba253960ce449f695fec6a82c6f"}, + {file = "charset_normalizer-3.5.1-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e06efa066f7dbadbc84ebc126a97c452a6451dfcf589d89d788484949e1cf795"}, + {file = "charset_normalizer-3.5.1-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:994e883d17c559cdfd38c84003c8b27d25424a1077272a17e7cd27bfe0bf57b2"}, + {file = "charset_normalizer-3.5.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:789b8982559ae28dad2356519f841655756cdcd96616410590ae0b17454ee64f"}, + {file = "charset_normalizer-3.5.1-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a951ad59cad9145664a730d3036b40b844e74d2d3683da40111463cd3a83845d"}, + {file = "charset_normalizer-3.5.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:55261ac0d2941c42f196dd576f543d87a8ee03cd6f5e30dfb4d807b2e3b9121a"}, + {file = "charset_normalizer-3.5.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5ca0555312ae2fe82715cada7fac375530c2f3349e1eaa1bcb33d0283ac79a18"}, + {file = "charset_normalizer-3.5.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2e9cf9253119d8e5d111f05d71626786fd3d6193817316eab1ca088cdb8593cf"}, + {file = "charset_normalizer-3.5.1-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:433c5a81eade63b47e522303bad236f59dba55ea6951746f5558355eeed8c75d"}, + {file = "charset_normalizer-3.5.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:13e3afe97712e8887cd516e960c63f0b93122971e5b5e4b2622fe7701771e838"}, + {file = "charset_normalizer-3.5.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb12fb2ba69ffa05f8695f61c69e591dc4b4a12ac3757ac8af8adb259bf56d17"}, + {file = "charset_normalizer-3.5.1-cp39-cp39-win32.whl", hash = "sha256:56490c595a28b1bb27dfc583e816152a9767721ef58b2c03b13f954d2f707420"}, + {file = "charset_normalizer-3.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:012a22b88a77ca2e59b98ac5889b0deb604147666032f45e6d6e217634d2550d"}, + {file = "charset_normalizer-3.5.1-cp39-cp39-win_arm64.whl", hash = "sha256:29880d17a8eb0b5cfdfd8944b468322928059aa35f1f5fa8ff22b149ec0b42f8"}, + {file = "charset_normalizer-3.5.1-py3-none-any.whl", hash = "sha256:6df0ec430f9a831772c23ca5a224cba36517a58a84bb32c32bb59a9fa67c47f6"}, + {file = "charset_normalizer-3.5.1.tar.gz", hash = "sha256:6117b84ea48435e5356dc737f5121485c30920ba43375fa7b434fd753df0eac3"}, ] [[package]] @@ -166,6 +212,8 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["test"] +markers = "sys_platform == \"win32\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -173,124 +221,140 @@ files = [ [[package]] name = "coverage" -version = "7.14.1" +version = "7.15.4" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.10" +groups = ["test"] files = [ - {file = "coverage-7.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3e3680291c4a1d0dadfa84a2c459576a4af5133abb617905714339a0c73138cf"}, - {file = "coverage-7.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a5274669f37f2343635a347b91a60777621341ab3378e9c6ac9335eee704bddf"}, - {file = "coverage-7.14.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cfe5a5fec635799ef33428f1e5e61bafa45a92a96190ba731561ba558ccc214d"}, - {file = "coverage-7.14.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:62a9f70b52e0b5a95cfef4a5c5641b06983cadc5e538a3feeb5c00211f523ac2"}, - {file = "coverage-7.14.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c18ebc343e15be53049b3a2dce38fe82d58f37e20ab9094b3a39c0aa4f6bb47"}, - {file = "coverage-7.14.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b84ffdf877644e7096aa936991efeed873f7f3df57b9cd001312b7668ab08550"}, - {file = "coverage-7.14.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e854312c4103f2ad4c0dc023b69b77ebfd2c89db5f86c4c94dc2353f9a92167e"}, - {file = "coverage-7.14.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c643734307300234fafa36bf2a040a7235f8f177ea1fd6ec1423aea6fb7b929f"}, - {file = "coverage-7.14.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:84ac9499e48700399a5dd0ea7085b5091961fec52c68d66b4ec0d3cf7f4441b1"}, - {file = "coverage-7.14.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:7f02d09f70776579b926d889a4c9c235070a1f47c40458aeaca563fae5acfdb5"}, - {file = "coverage-7.14.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:ce66d8e46da2bb5ee313a745cbd2e391d319176c1f7a9451bfcd3a2fb920859b"}, - {file = "coverage-7.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c912c259304cfb5ee584481cfb7ce1ff932b4d61e6c9140b8f19cb7b5ed82332"}, - {file = "coverage-7.14.1-cp310-cp310-win32.whl", hash = "sha256:1238cb94638e610e972c60dac68e813f868dc7d6e982535270558443058d9d59"}, - {file = "coverage-7.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:fc459e5d73be2d6332fcfe8dbf3d8994671fe33c700f4565988ecfa511547253"}, - {file = "coverage-7.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:478b5bcd63c2e1357c5c7e16c070690df7b07f676b1c114d7b93e533c664309f"}, - {file = "coverage-7.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a24a81f9715ee42ef59a316cc11611c98fe23920f7c81861315c9f3ff4a230f4"}, - {file = "coverage-7.14.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:196a13319ad88d6d8ef5ab489ec4f44ddde2143c0c7d5b27786f6c3ffd56a7e1"}, - {file = "coverage-7.14.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3d452fd08b5c72c5167c93e6867b5c08500bd40f2a21e1e854a500550b6cc36f"}, - {file = "coverage-7.14.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23bf7fa51ac02e07fc7c96849b82946da47ae862dc8f86d183b2a4864fc38129"}, - {file = "coverage-7.14.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bcaa50684dcaadfa599ac48f81103c756d791cfd85c97203d2217c593d48b860"}, - {file = "coverage-7.14.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4ea1c034f95c9b056e856b794630b17f9fa3d57e4800ff1e503d3be0f9c9078c"}, - {file = "coverage-7.14.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c7e057326434e441306226fbeb5d1aaf14a2637efe97ba668306635835f32ad7"}, - {file = "coverage-7.14.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:59baf88468dbc8d63b1887afd92bda52e40bb1561696e5819670601403810cec"}, - {file = "coverage-7.14.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d34d75f892b3ab73ba11cab5442cce7b3e168fd64162b16f0e1e0d09c508edef"}, - {file = "coverage-7.14.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3a56abc20a472baf0304c455721bc601477440d28ecfde8a03dde79ede07e0df"}, - {file = "coverage-7.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6a3cb83d1552c0cd1b4906655b6a33fd4a8473229633a901c6b73bf86914dee9"}, - {file = "coverage-7.14.1-cp311-cp311-win32.whl", hash = "sha256:10274a1fbeb8ec5d72966e17bb198a3104257aca4ac09d98667c5f8aca8c8548"}, - {file = "coverage-7.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:87ebdf787d4888e3f3f2d523eadc6e18c6d18c6d0eb173801a189641627fb37e"}, - {file = "coverage-7.14.1-cp311-cp311-win_arm64.whl", hash = "sha256:dd34767fa19848d35659ffc0a75314f58c7af3f1cd87ec521e8292a1238398a3"}, - {file = "coverage-7.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a06c76364a9360e33d6d23769aefdf7f66f38e2ffb60ceb1baaa4989d83b695c"}, - {file = "coverage-7.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fad54e871165f6ec2f536063ac74c3104508a12963e64072ba44bd822de52b0c"}, - {file = "coverage-7.14.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:84b535f00655ecafe1d929d1fb00ed5d6fa3051ea643ab2c161a3887b86f294b"}, - {file = "coverage-7.14.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6b6b0853b895fe0e98cbfc580d1ec3393d9302b4b1e96a77b3f5c91fdab899e6"}, - {file = "coverage-7.14.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:442cc9c952b2df400cda54bb04ab87330cf2cd08a8692cbbea36773531eb6f37"}, - {file = "coverage-7.14.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8270544c361ed405a27a060dbc9ed2c124b084d96dfdc2d9a2510482aef981ad"}, - {file = "coverage-7.14.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:48b283b1dd6372e8de2a7a9a4c4d5dc06f4d4fd209b876f3c88a7a205a0c8f84"}, - {file = "coverage-7.14.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5b0c99ba93a07d56f6df340bb79be53202a082b2fdb81bfe6190b741a3470d54"}, - {file = "coverage-7.14.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e471bc5769ff073b058cfadb0d736b56ce067c8560eabeb0da88462df98c23e7"}, - {file = "coverage-7.14.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f497a1ea81d4cd7c10ddcaa685135b9aabd291af3d55775a9ddf3cb7a364cdd9"}, - {file = "coverage-7.14.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2222be86d0b54f5dd5a38f45f17f315f737245e857bf0bdedc70734f84a13c02"}, - {file = "coverage-7.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:85e85586565842f6932abebd4c18bcb1074223dc0b3576e7d173ca710622813a"}, - {file = "coverage-7.14.1-cp312-cp312-win32.whl", hash = "sha256:4a28fd227808366b196a75476dced2eb35b351d6766ba9c858dc93319e87f4f1"}, - {file = "coverage-7.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:54acdb6674a4661768d7bf7db32dfb9f46ab1d764f8aba6df75ce1a6a088724e"}, - {file = "coverage-7.14.1-cp312-cp312-win_arm64.whl", hash = "sha256:99cd41ff91afd94896fea3bc002706b6ae4ce95727d06e4a0f39c0a8d8bd8b1a"}, - {file = "coverage-7.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:be9f2c802dcfce3f71298303aa5dad0dce440a76c52f2f60dacd8656dab78793"}, - {file = "coverage-7.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6223a72fd0e4c7156353ec0f08a5f93623e1d3034d0e2683b9bb8ea674131b1d"}, - {file = "coverage-7.14.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7279d2110a28cebc738b6459ecda2771735a4c18465fbbd36b3288fe5ed92247"}, - {file = "coverage-7.14.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9eeb3fcbc13ba40dfbdb22d01d196a28e9cef9ed4c29b60061a1e0e823a9929d"}, - {file = "coverage-7.14.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f0cfc27c539f07cf5c0a4cfe211d0b6cae039f8f40526dbaa71944e64b50a7b"}, - {file = "coverage-7.14.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:221c70f316241a78e77e607c227cefc8808d4e08f28d99c04f35694690e940be"}, - {file = "coverage-7.14.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:da028256b04ec30e5e0114b6f76172938c313991f0a2d3d894271315cf5d5e43"}, - {file = "coverage-7.14.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76a085d7005236a767e3426148b2c407e53ad61695c562f8a81da2d373324901"}, - {file = "coverage-7.14.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b553d04b5e778a8e56d57eb134aff42a92718ecba45e79c4764ecfa40efd92ff"}, - {file = "coverage-7.14.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:46f714d2fb8ae2f4f29f23ada7f1e79b759fff5a70f94a1dac23af204c3ec9e4"}, - {file = "coverage-7.14.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:1896f5e19ff3f0431c7ce2172adc54890fd97f86b59ced8ca1649145d9ffe35d"}, - {file = "coverage-7.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:62fd185ef9df3c33d1c8178c5af105f762afbad96038de9a4ae100aa6297ca33"}, - {file = "coverage-7.14.1-cp313-cp313-win32.whl", hash = "sha256:ab4af6352741a604c431c6072fce5bee33bf0f20dc7a56618d6bf6bb89e9810c"}, - {file = "coverage-7.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:7af486dabe8954d03b087f0021540897afe084f04e16ff5579e08cc46f871416"}, - {file = "coverage-7.14.1-cp313-cp313-win_arm64.whl", hash = "sha256:2224f89ffd0c5605ccce1ed7a584da162bc7c55f601ab1c946bc9de31a486b42"}, - {file = "coverage-7.14.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:de286598cc65d2b489411174b1faec2f5a7775fb3201fd925db2a76b4030f37d"}, - {file = "coverage-7.14.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:042c46ded7c288aeb07cf14a28b6c1e10b78fcba40171c3fa1e939377eeef0b5"}, - {file = "coverage-7.14.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f4ddbe407477f04c45115d1a4e5bc480f753553b534d338d4c3358b1cdd0ea52"}, - {file = "coverage-7.14.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d13e6725992e2d2fd7d81d4f5241952d13740121dfd501da09201be39b2c003a"}, - {file = "coverage-7.14.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f747dc8edcfe740130f28f32f3995e955494285717e86ee25af51db2219df08a"}, - {file = "coverage-7.14.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ced2f09ef276fd58611a1ef502164ad266d2b75174e5a40cabbdb4033f9f6cf2"}, - {file = "coverage-7.14.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b84800013769a78ccb9ef4659402e26d06867e337b61ec365f77ad008adea80e"}, - {file = "coverage-7.14.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ea8cd6ca0ee9f616aaef3afc6882e32c2cbf18b00d96313ffd76af650574034d"}, - {file = "coverage-7.14.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:aa5e304a873fabddc11e484e9b6b738bd38bd7bed17b09aa84eecf5332e8b8bb"}, - {file = "coverage-7.14.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5a1c5215be81035e629d5bc756650634d0bf31991038db7a0eccb90f025ce16d"}, - {file = "coverage-7.14.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:79058c47dae6788504b5effb319961bcd72d7240551464b91d474bc0ed186d69"}, - {file = "coverage-7.14.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:370c5afae3fa0658e11694a32b24c2778f6bc2d17718121f94ee185e69f26b54"}, - {file = "coverage-7.14.1-cp313-cp313t-win32.whl", hash = "sha256:3758dd0a7f1fa57365ef2e781df0f0731d38b6e3772259d13dae4bd8a958d4b1"}, - {file = "coverage-7.14.1-cp313-cp313t-win_amd64.whl", hash = "sha256:6ff665fb023a77386fe11685190cee1f60a7d635994a30d9b0a061533d470fce"}, - {file = "coverage-7.14.1-cp313-cp313t-win_arm64.whl", hash = "sha256:17a5a241e5997621a956a7f402a7433ef4221e5152809b785bec79e2323799f1"}, - {file = "coverage-7.14.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d5ed429d0b8edaac649e889b4ffcedb6c80b06629a3f93050e3dddfb99235bee"}, - {file = "coverage-7.14.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8011224a62280e50dab346960c03cf47aca1a1e09e608c0fb33fd6e0cc8e9500"}, - {file = "coverage-7.14.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:12c42ec1e14f553c4f817e989365982e646e27211f10a0f717855b94a79c8906"}, - {file = "coverage-7.14.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:06144cd511cf2624873a035c5069cf297144f6e77a73ee3d7a55b605ec5efb42"}, - {file = "coverage-7.14.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a311d8e1da24be5c1ccf85cbfb06315dbaa1703d5a1eab3f6432c72b837917c8"}, - {file = "coverage-7.14.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c79cead5b5bc584d9c71451cb984d0e3a84e0c0937379c8efcbf27c8d661b851"}, - {file = "coverage-7.14.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dcbf65f1f66a26cdd88c35cf68fb4729c5d1cd2e88added72420541dfb212034"}, - {file = "coverage-7.14.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fd86572566fb40189a8260446158235159bc7a82dfbc87a3b39cf4fb57fcec1c"}, - {file = "coverage-7.14.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:7771b601718fdde84832c3a434ca9bbf4ae9adbc49d84198b4110700c3c77c36"}, - {file = "coverage-7.14.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:39b21e212c55af06fa375e3dbf90a8a8e38792f3a910c580066d23563830ddd5"}, - {file = "coverage-7.14.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f2302660e32562a532b442480121aef8aa61a5bdb20b30bf0adab29f10a5a4b4"}, - {file = "coverage-7.14.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:03a6f93c1ec3b7f2e77b5dbcc5573a2c21f12529a5c6bbe0f16f72303cc2fa4d"}, - {file = "coverage-7.14.1-cp314-cp314-win32.whl", hash = "sha256:8a3ce026d73290f42f08dafecbd82c193a74df280461fbf97300fec51fd133ee"}, - {file = "coverage-7.14.1-cp314-cp314-win_amd64.whl", hash = "sha256:114c95ef29302423b87d159075805f4ab973254a2638a5d7d046c94887cc87d7"}, - {file = "coverage-7.14.1-cp314-cp314-win_arm64.whl", hash = "sha256:a07891c3f4805442b31b71e84ba3cf29ed1aa9a428284e06deeb4b23e5b46343"}, - {file = "coverage-7.14.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1101a5ebb083aecb625ebb6209d4105b58f647b093cb2dc8122d7b33f743cfe1"}, - {file = "coverage-7.14.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:851b9e1e4e8a4608e77c79714b2e77c0970d2ed7202a05e92ae407817481887b"}, - {file = "coverage-7.14.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d5b89cdfb2ee051b71e8c3c70bd81a9eff81100f736a269136fe1a68efe00474"}, - {file = "coverage-7.14.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0177614a0370f227888b4e436a7c55686d6a9f90eb1ade2b624ba685a1686e86"}, - {file = "coverage-7.14.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d69af5dea2de76fc485a83032a630523f985198b7e25be901ec60181587b01e"}, - {file = "coverage-7.14.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:35ab22d91de736e8966b980dc355cbcdd2c6dbbcfe275f9a2991bc8a91b3df65"}, - {file = "coverage-7.14.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:357d4e32935c36588aaba057d734fa32428c360c9fc2e4442afbf1b646beee6e"}, - {file = "coverage-7.14.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:51bd64741cc6fa065abd300ede1afe5a5291ece9c31da8b24884deda48bcc3f8"}, - {file = "coverage-7.14.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:9132cd363a68a4c3daa7c8704a654b1e39d3360f6f5b8ddd470608a945236c07"}, - {file = "coverage-7.14.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:07c6290b1697b862c0478eab545eec949a0d0e4d6d03497f446d706da3b4f2de"}, - {file = "coverage-7.14.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5ea0c297e27133853b4d8a3eb799bff5a2dbd9f2f41537a240d337ac9b4df890"}, - {file = "coverage-7.14.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:01b7733daad0237daa01ef80fe2dfceffc911e6a17fa7b55d14aa8214eaaaecd"}, - {file = "coverage-7.14.1-cp314-cp314t-win32.whl", hash = "sha256:6adc5a36984624a70bf11d7184e20fa0a49aa7c47ffab43804106a1a695ea22e"}, - {file = "coverage-7.14.1-cp314-cp314t-win_amd64.whl", hash = "sha256:ddf799247318f34dbcd2efa8c95a8d0642674e926bb1774cf9b63dfd2a389d1c"}, - {file = "coverage-7.14.1-cp314-cp314t-win_arm64.whl", hash = "sha256:145986fe66647eb489f18d9a997567a3fd358584c4b5a808769113abc07466af"}, - {file = "coverage-7.14.1-py3-none-any.whl", hash = "sha256:a252f21c27e38347e60111a3266b03827422a7d5525951aceee313aa68bab1d2"}, - {file = "coverage-7.14.1.tar.gz", hash = "sha256:30c08f7d90415aa98b3c990385dea2939b0da55f38515e5b369b83655f8523be"}, + {file = "coverage-7.15.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d0be6daac4cce6b8c8dc65886bae1b082ddbca4da8e5cbb5e15166acf253e264"}, + {file = "coverage-7.15.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b24e078eabcd6a9caa8b0713f9bc1eeb310bcc960a29d45a3b4fcd4b16d5b11d"}, + {file = "coverage-7.15.4-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cfe20cc8cf8821d4fe54f89106cbf06aa27f37b5bbe3535568065a81539b4150"}, + {file = "coverage-7.15.4-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:83cf06cdd687677742caff1a9134833b7a8b75f111519d2cb0e0ba1b9a851e15"}, + {file = "coverage-7.15.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8fa4de68e2a752468ff14b4e15db7def689a71be759e826a31ccecbef69c5fd0"}, + {file = "coverage-7.15.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4dff9daa47d83120c3ec38ce921214242944a832aa04e903e50b5b7ebac8972d"}, + {file = "coverage-7.15.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a093fd37229918976f602aa07aa59e0973cde82186f220c8e197f721f5be0ce4"}, + {file = "coverage-7.15.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:317db01a2cb02552fd67e2b1cca77a4b528a2a277176c5e0bf2cecbb639d3f54"}, + {file = "coverage-7.15.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:8ee3838dcb656602c3b51e16aed9bfb0822f8d8d6d1c5966d32ec8c104be8e20"}, + {file = "coverage-7.15.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:425920379052ff1fe465268f3361d35804a241bbdd5a1b592c8cb60df4c52325"}, + {file = "coverage-7.15.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:69bb2400abef928e365ea7d4d9925169ada78ed2295546780002d4b65de3df88"}, + {file = "coverage-7.15.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:81661f82d302484e3119e7c80c519c02fa9bcc2a6b339baf67d67bc89c580f04"}, + {file = "coverage-7.15.4-cp310-cp310-win32.whl", hash = "sha256:cb476b2e828ecb71cb6b6a928d23fd20a7ddb501188022dae1c37499149cc338"}, + {file = "coverage-7.15.4-cp310-cp310-win_amd64.whl", hash = "sha256:3fc2130bf37df31852a8384f12601563a45a0024bccc6624f38355cba7a8d360"}, + {file = "coverage-7.15.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bbac5abad70df71019988f83f26ac7092ff2642975def4429e98dc7585ef3490"}, + {file = "coverage-7.15.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:357a173465c7ce028d07a95cc2b63b5bf59f50ecdd5ad75c5cbb78ada984048e"}, + {file = "coverage-7.15.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:21b803935e2efc3acebe9697197a294fccf5dc4e5382bd6369542ff7a7d2a1d7"}, + {file = "coverage-7.15.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7a2b580774a4786c1053157c0165e04476e03ff293993d7c148eee784a94bae6"}, + {file = "coverage-7.15.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a9464451c4efffe8d47ace5a540b10b0dc10e879066290f8600872b7f54a419d"}, + {file = "coverage-7.15.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de602f34123c2f4af1c1869c6dbbbd60da6d5983bf01937367295d135cccbfce"}, + {file = "coverage-7.15.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6879ded16a27f3eeca19b900c147e81616e7054db451471a611b2755ee5249f7"}, + {file = "coverage-7.15.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:986be58c3ab54aae8d3496a6225eea74f760fdbe739b38bd442c7e8d133aa53b"}, + {file = "coverage-7.15.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c6103639613fe6c1e989082948419bc77a2d26b6c825c99d7fad25f7d3d87afc"}, + {file = "coverage-7.15.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d3af93dddb5659276c63bc16ac6466ac2033a70ca816097bbc06345b8ccdf571"}, + {file = "coverage-7.15.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b10075e5421d04265766a6d1dac809bbeb8a946fbb23c8f82c227409b2190719"}, + {file = "coverage-7.15.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a67a9f78b2942d87ba8ce3059c642164d2aedd65337377fb52fe9803656bc5c7"}, + {file = "coverage-7.15.4-cp311-cp311-win32.whl", hash = "sha256:69484d1aca26e322e1c3ce03f09341e84524ababad2d7202161738d83cc9f82e"}, + {file = "coverage-7.15.4-cp311-cp311-win_amd64.whl", hash = "sha256:63fd6fcd1dd6e158f7eb78606e72933b3f6d01e7b747f99c6c12d764307a0fdc"}, + {file = "coverage-7.15.4-cp311-cp311-win_arm64.whl", hash = "sha256:ea82116c9893fa89e929b7f197ee5a1950a76e91cc5c85ba503fc02379d04890"}, + {file = "coverage-7.15.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d4fedd1f7f428f9fe83b1ead5e7cc87a43427be31aadafbac3ac0636dc7abb22"}, + {file = "coverage-7.15.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:37e2f0cdf58e2e1fed4e4d5a8f8786ae2f7eb80b478016876667dc4a01d60a97"}, + {file = "coverage-7.15.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fb55d0e70bb15f2e81477613627286581414693d74ac7963c93a790dd453ca9d"}, + {file = "coverage-7.15.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:899b9da30f3c6c336566e3707495bb23e8302d39d862f01fa78c48b99b9437e2"}, + {file = "coverage-7.15.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d15715e8c46552827e5e4f30a35575a2dbcad14454cf3284c54483946bd16931"}, + {file = "coverage-7.15.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:002a438859f7b430bc99afeaf01a6d187dad1d0dc907b64cdeffc632a5db8fd8"}, + {file = "coverage-7.15.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e4193a04b518f7968f3099755f5509ee7cccc6dc2b92a6b14841934d22e222c9"}, + {file = "coverage-7.15.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e98dcc55d572b38e69d117da7e8e8efb8500f1f5eaf81ecd460a63220790b839"}, + {file = "coverage-7.15.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:af6c538498ce66c10d3fd541c2a8d5b03da5850355add34e6cba564210cb9e72"}, + {file = "coverage-7.15.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1d10025d96ea89fc2f73714dbc4cbd433fe012c1ac9e23f895d7728b238b6e52"}, + {file = "coverage-7.15.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d802e1947603162ded419bff83ac7489820355d2b856dfb09206574e3a37ac0c"}, + {file = "coverage-7.15.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c2de40895718f91951b86712b4c5b694acaf9a0a49be13874896f599a1eed3f4"}, + {file = "coverage-7.15.4-cp312-cp312-win32.whl", hash = "sha256:5c3431b2161279b7db5c2a1aa58ae02e5cb8c3c42d93a5094be3f5537bd5b11b"}, + {file = "coverage-7.15.4-cp312-cp312-win_amd64.whl", hash = "sha256:6befeab5fb2b51c958ca4ac6c5d141a1e8240f4f76e46350f1911963deda49cd"}, + {file = "coverage-7.15.4-cp312-cp312-win_arm64.whl", hash = "sha256:67bc345491ab55b837277d76f5775d057e8c7f1ac44d890d8c2c82adde258c6f"}, + {file = "coverage-7.15.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c705b28feb2775dc82a25f1d473a370bc37ff93f5177f4e29ce2425f560f6921"}, + {file = "coverage-7.15.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3ff205ab5e3ecc670f6a4dd19d9cbf12ede53dd41cfc1e15716ec961ea6d314e"}, + {file = "coverage-7.15.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5172326e861a38b48b48befca15e0f477a26b283337a33a739c8fed229934e36"}, + {file = "coverage-7.15.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:12b59c90084e3234fb11184886bf4a40f4f16a8c8f867be2e087b81f8e8868d4"}, + {file = "coverage-7.15.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:349062d66f00b40fa2c1c222438bad25fabf755631b5d82937fe985c8008615c"}, + {file = "coverage-7.15.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4256ced708e598e05209bc1a8ab4074e04a51dba4c62fb45926a229af675ace7"}, + {file = "coverage-7.15.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d80f974b20782d9612c8b4c9beeca867074c7cf4079d1419843fa25a26428b25"}, + {file = "coverage-7.15.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2e179f19bfe1d31f8eeeaa12990194d761c4f62f0759661000bca6cd8729f40b"}, + {file = "coverage-7.15.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8bc16bb47b7679670eceff71d78bfb7d6e5b143f6c2cd117487ec7c75e0d4b78"}, + {file = "coverage-7.15.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1cd685005cd2c4200adfc14cf39a603b9320efab3f18a8f7f156d20c9cc3345f"}, + {file = "coverage-7.15.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:337399ad2c93b3acd2a937627dae8b3e86b66707cd3d3e856347999aadf1ef8d"}, + {file = "coverage-7.15.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:96e257121228ec5cd2bb919276e94ac11074471bc37d68dbae0e8308cce15fff"}, + {file = "coverage-7.15.4-cp313-cp313-win32.whl", hash = "sha256:c65a9e0dfc6143491879da4e13b5e30f8be192055de508d737fb14601edbd22c"}, + {file = "coverage-7.15.4-cp313-cp313-win_amd64.whl", hash = "sha256:2ff8f5e9b8f7a94f0c11c45631eee103dbcb7d63274edd12c56efe1be690b3b4"}, + {file = "coverage-7.15.4-cp313-cp313-win_arm64.whl", hash = "sha256:6e0a8a5083b096487d6cfced94cdd514d8f5db6f113610fb36c0620edb1028cf"}, + {file = "coverage-7.15.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:770e9325ab5ea6d56f77e59b29ecfe0ac20b57a82a601876f90494a4dda0386f"}, + {file = "coverage-7.15.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d12b33a3a50a1676b7784dc8d00a0c6d66a9f2add4b85a041c19b6a7e53ef23c"}, + {file = "coverage-7.15.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5669c8378ebde86f5def7a25d29586631b58acc27ffde04399f678f3dfc6e082"}, + {file = "coverage-7.15.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ff97a14362eef486483ed44042ca2027ea257df6ff768e62358ee0c9776925ac"}, + {file = "coverage-7.15.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a325e815318638aed1655d9c06e6d7c2d3d46c09231ce988070428a8762d734"}, + {file = "coverage-7.15.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:474223409d88eb20d2d6a0d37ea60e8647a65a90cc008dc1f0410af5f64f1e0d"}, + {file = "coverage-7.15.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7f2f62ae3cd189dd2e13aece758c57b3eecbd27be070dbd4cbd10936049e5dbf"}, + {file = "coverage-7.15.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:39ece820e29e0a2ba34b3ecb3be83c27e997eed8926f2ba6fe7ce7a0bda5843b"}, + {file = "coverage-7.15.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f21b56dcace11dfe013014201f577dcd592b2a9b72182d930361b47cf6f73f25"}, + {file = "coverage-7.15.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:93a3a0b662abcc10c73a47cbc72cd60f63618d6989fb2d1286e50eacd974f303"}, + {file = "coverage-7.15.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:141fae2cabf5569b782c10afc4c850ce10f618c13f8db54765cba99cc839da1f"}, + {file = "coverage-7.15.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:81294c7e6ab30c5f74c0353b11b2fd6320e72d9bee6ac73b357caa8b916323a5"}, + {file = "coverage-7.15.4-cp314-cp314-win32.whl", hash = "sha256:7bbd7d6418e0dab31a206af5203bd43ae36edb8e7fba1940b055d3e9249290d7"}, + {file = "coverage-7.15.4-cp314-cp314-win_amd64.whl", hash = "sha256:f0204ed122758782970526057093f448051a39db9d810d4e344bb87a3546f425"}, + {file = "coverage-7.15.4-cp314-cp314-win_arm64.whl", hash = "sha256:9e71e7bc71c686a123347ae47a0de33a175e797a85bb57b791492adf4eec8ed8"}, + {file = "coverage-7.15.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7c922735321eef3f87c280a3d39afff6b646723a2880b862cda4ac7a093b8aa8"}, + {file = "coverage-7.15.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f41c17c4668a655ce96d090d8d5ffdc24ef64b5a02f9753884d08483e8a4a41a"}, + {file = "coverage-7.15.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:46822e9b6ff1c6a72b518c162c44a8f45a61a1d609c51084bf5b16c023c5037b"}, + {file = "coverage-7.15.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3d6f4955b73b5445271379a59e3792b0d978f42d4a01e0cf7a67d9c33a3bb0a5"}, + {file = "coverage-7.15.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3fc9e047706fb4a9abb54f719d3aa643e80e5bb3818182c40aee01ac0f0247ba"}, + {file = "coverage-7.15.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05e491d4f3165d62d4f5c8fd48dfeabf2ae8f42cbbd484319af33ea851b78982"}, + {file = "coverage-7.15.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:226c66e80ec0598d3b9b4874123df167ccca342aca8714f77cac6829688ee09c"}, + {file = "coverage-7.15.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ac41cc14bebda0dbfb0628036b7f75706935c95bcc07fefe9a0f93614aa60a57"}, + {file = "coverage-7.15.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8af623e5cd92080acddd02b38f2f406a2c3a0893c38950b211890361448fbf26"}, + {file = "coverage-7.15.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:07545711d4f0f32852a18f18ad11f76f0109909d09e78b9008b4cfc67e829429"}, + {file = "coverage-7.15.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a0865421cfdc53654b342d515e5a233187590882d20b95752150e53f65460017"}, + {file = "coverage-7.15.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:460115e32ee40566476db5048f9bec1e842c127ad8e6f8be745aad3ac9cbc839"}, + {file = "coverage-7.15.4-cp314-cp314t-win32.whl", hash = "sha256:cbde877ef9dd7baf272b9bfef2b8a25edd45d9170fc326951dd20eb480335e85"}, + {file = "coverage-7.15.4-cp314-cp314t-win_amd64.whl", hash = "sha256:3da9e92d1c551fd7563833e9ade686efb0c4b7363ab7681a94283958c950bf5e"}, + {file = "coverage-7.15.4-cp314-cp314t-win_arm64.whl", hash = "sha256:3a54f5a0d85050c73a38f6793090ee83974531e67fe5e57a1da9bee11398aa5e"}, + {file = "coverage-7.15.4-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:2c9872e4d9dc5d3cf616bf4b382f5a00359305a5be666a3dd0b5cdb4e49597f9"}, + {file = "coverage-7.15.4-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:e101dbb4b9b72f0cddd8cdc8c9c5b47f456766f5e0ac82dbfb75e5c55409b78a"}, + {file = "coverage-7.15.4-cp315-cp315-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7d1abebdb047729e852b9c77a00497dfbeb11eb3a117e037d7dbc3ac8e5f5c54"}, + {file = "coverage-7.15.4-cp315-cp315-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d28a4a899354d0ea6214cc59b4fa19eefbce1b9ff1688ab579acf49e894bd3fb"}, + {file = "coverage-7.15.4-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffb3c2aacea411cc7e1d27712490c11108e2de1d39019ae32915493a59a8b9ed"}, + {file = "coverage-7.15.4-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9447978a92f405d301123cfd39ff49895490efb769a758fe2734c7f631bf8ce"}, + {file = "coverage-7.15.4-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:050467a7983b8e2fe7dd41a78bb30c3e7f8c0b8cafda14b1c46f8b5e3cf2dd3c"}, + {file = "coverage-7.15.4-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:d003b7a5708ddad5c206c79607a6b92abb6fc13c57d99d8a4468cc03a2941ced"}, + {file = "coverage-7.15.4-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:c38efe30fd74e5c19e9433f11fb1f5dc9c6522770971b7c6145bbaa413dc8800"}, + {file = "coverage-7.15.4-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:1f4f826d70f772ab8b0c052329580d7fe8b8abd191e4ce0c8f81aec6614665d3"}, + {file = "coverage-7.15.4-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:4a4bf917c9953f57c957be31c1cd504e3bd2f34d4a352b9d391a3025336f6768"}, + {file = "coverage-7.15.4-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:1c9bf40ebef178a45192c75c4964760bb261b0e6ad725da5fc4c93f674f19753"}, + {file = "coverage-7.15.4-cp315-cp315-win32.whl", hash = "sha256:43619d04c3671792d2c4706ae8bf45e265dc87bbd4078189ef8b847ea1e74be2"}, + {file = "coverage-7.15.4-cp315-cp315-win_amd64.whl", hash = "sha256:be619439dbcd31a2eab10b32de9fff62c26ed4bab69dc32b8363fdaaa0882809"}, + {file = "coverage-7.15.4-cp315-cp315-win_arm64.whl", hash = "sha256:def597967dafc2e8d97c9097ea453c464e0bb8ed38f193a43070f10dc623bb6d"}, + {file = "coverage-7.15.4-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:c7dbc748ac8a1e3e59a2b28bea47675e6e778081dbbf081bde0d75def2fcbe1d"}, + {file = "coverage-7.15.4-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:2413074a5ecbb61a01a7888fc72db0ca324d13588c5b38bc0dd8564cdcdfea26"}, + {file = "coverage-7.15.4-cp315-cp315t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4e6f6f632b7b2f714bf7a1346e8f97b650ee71f3c298aaad42a2ab60f0f07645"}, + {file = "coverage-7.15.4-cp315-cp315t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8df457da2249d3c75ca2e5e835d59c725abfe92d27fdff6cd99eed85b51d5e9a"}, + {file = "coverage-7.15.4-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:050f66a08805acb5b8a23c6d4a517b1ecf82c08e81ed0e4bd727df065e5c6624"}, + {file = "coverage-7.15.4-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1587fb771d1ccceef708fdde1e5af8c7ed24b486b61d13a321acb7d8145390aa"}, + {file = "coverage-7.15.4-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8b4f1c3a69ca580f3fbd6b2046915f536d7f586874f25c1bb23add2a3c88d50f"}, + {file = "coverage-7.15.4-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:ffb58d7eff5b7f6ecc6fa21d6288ab7f968a212cb67d682c269c09b9eba3b66f"}, + {file = "coverage-7.15.4-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:d9df165544774574ee004b953023d1bebada1894a80b1052a43d798b0f676e67"}, + {file = "coverage-7.15.4-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:f9de0a24a4079b53e523b5c5e2c5945ec251ab486652659955187cf255a259bc"}, + {file = "coverage-7.15.4-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:150089274bdc9f940628552cb92844e0223c987f1902ab8efe9f45a2ec758d88"}, + {file = "coverage-7.15.4-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:a58a94fed5da6997d258e8f7668c1e195fbd04a691d781b7558f1e468f9e68bc"}, + {file = "coverage-7.15.4-cp315-cp315t-win32.whl", hash = "sha256:ebd5a6d8466ff30836572f3ba2cae8a5e8f85029b1c6d5e2ed338dc472a5166a"}, + {file = "coverage-7.15.4-cp315-cp315t-win_amd64.whl", hash = "sha256:288bde2a2d7ab6b6c2d7252fcde8b524387f2d970bdba9658fc6f8bbcaef0f9b"}, + {file = "coverage-7.15.4-cp315-cp315t-win_arm64.whl", hash = "sha256:68be5e1de60ff13c9095bbec0e5a7fa45b33b101752215b91345ea1f61c4a278"}, + {file = "coverage-7.15.4-py3-none-any.whl", hash = "sha256:964730a1e9de9c0cf11be6a1a3c79ce419c34882842abd256086ba4698705e84"}, + {file = "coverage-7.15.4.tar.gz", hash = "sha256:0548198fff07ccf4faf469520bce1c2eceb1ce3e62891921138dec10907f9d00"}, ] [package.dependencies] tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] -toml = ["tomli"] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] name = "desktop-entry-lib" @@ -298,6 +362,7 @@ version = "5.0" description = "A library for working with .desktop files" optional = false python-versions = ">=3.10" +groups = ["dev"] files = [ {file = "desktop_entry_lib-5.0-py3-none-any.whl", hash = "sha256:e60a0c2c5e42492dbe5378e596b1de87d1b1c4dc74d1f41998a164ee27a1226f"}, {file = "desktop_entry_lib-5.0.tar.gz", hash = "sha256:9a621bac1819fe21021356e41fec0ac096ed56e6eb5dcfe0639cd8654914b864"}, @@ -312,6 +377,8 @@ version = "1.3.1" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["test"] +markers = "python_version == \"3.10\"" files = [ {file = "exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598"}, {file = "exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219"}, @@ -325,17 +392,18 @@ test = ["pytest (>=6)"] [[package]] name = "idna" -version = "3.18" +version = "3.19" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ - {file = "idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2"}, - {file = "idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848"}, + {file = "idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4"}, + {file = "idna-3.19.tar.gz", hash = "sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15"}, ] [package.extras] -all = ["mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] +all = ["coverage (>=7.10.0)", "hypothesis (>=6.141.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.16.0)", "ty (>=0.0.37)"] [[package]] name = "iniconfig" @@ -343,6 +411,7 @@ version = "2.3.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.10" +groups = ["test"] files = [ {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, @@ -350,13 +419,14 @@ files = [ [[package]] name = "markdown" -version = "3.10.2" +version = "3.10.3" description = "Python implementation of John Gruber's Markdown." optional = false python-versions = ">=3.10" +groups = ["main"] files = [ - {file = "markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36"}, - {file = "markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950"}, + {file = "markdown-3.10.3-py3-none-any.whl", hash = "sha256:fa6c92a00a4a3c98b22728c64a935ae1928250ae65058a6ded814d2cc29a4cea"}, + {file = "markdown-3.10.3.tar.gz", hash = "sha256:3589362618f743188b4d955b874402bc814f4f83f544dc207719f4baa7d9c45f"}, ] [package.extras] @@ -365,13 +435,14 @@ testing = ["coverage", "pyyaml"] [[package]] name = "packaging" -version = "26.2" +version = "26.3" description = "Core utilities for Python packages" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["test"] files = [ - {file = "packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e"}, - {file = "packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661"}, + {file = "packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c"}, + {file = "packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79"}, ] [[package]] @@ -380,6 +451,7 @@ version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.9" +groups = ["test"] files = [ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, @@ -391,13 +463,14 @@ testing = ["coverage", "pytest", "pytest-benchmark"] [[package]] name = "pygments" -version = "2.20.0" +version = "2.21.0" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.9" +groups = ["test"] files = [ - {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"}, - {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"}, + {file = "pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9"}, + {file = "pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c"}, ] [package.extras] @@ -405,13 +478,14 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pyproject-appimage" -version = "4.2" +version = "4.3" description = "Generate AppImages from your Python projects" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "pyproject_appimage-4.2-py3-none-any.whl", hash = "sha256:d6892643db5759dc06531a4546bdab404a519c63814c060f8749979a8625d9cc"}, - {file = "pyproject_appimage-4.2.tar.gz", hash = "sha256:6b6387250cb1e6ecbb08a13f5810749396ebe8637f2f35bf2296bfdd5e65cd6e"}, + {file = "pyproject_appimage-4.3-py3-none-any.whl", hash = "sha256:b9fdc6d1829ead1ca3021fe6d8c9e7c2830b43426c427d91b3d07c5480fd07b6"}, + {file = "pyproject_appimage-4.3.tar.gz", hash = "sha256:03a6afd07672f406f9df18ae3f424f6a370139bbe8f463f729a76fad4a42da3a"}, ] [package.dependencies] @@ -421,58 +495,61 @@ tomli = {version = "*", markers = "python_version < \"3.11\""} [[package]] name = "pyside6" -version = "6.11.1" +version = "6.11.2" description = "Python bindings for the Qt cross-platform application and UI framework" optional = false python-versions = "<3.15,>=3.10" +groups = ["main"] files = [ - {file = "pyside6-6.11.1-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:537682c3b7530817203e667c1f5a2f00486b37bf52c52eeab438544c7a0917f6"}, - {file = "pyside6-6.11.1-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:b1fc521ba2bb5109425ab8add06bddbdd524abcad06cfa012cc39a22a189feb2"}, - {file = "pyside6-6.11.1-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:75f0005c3eb95c07cfb65522ec50d0815ac007a96482c21dc3cb4b4c04895d84"}, - {file = "pyside6-6.11.1-cp310-abi3-win_amd64.whl", hash = "sha256:0968877ab1fb4ef3587a284da6fe05e8647ada56a6a3750b6395188e01f4aba6"}, - {file = "pyside6-6.11.1-cp310-abi3-win_arm64.whl", hash = "sha256:acee467cb5f256cc47ebb9d815a054c1d8416da380c191b247a76d164aa3f805"}, + {file = "pyside6-6.11.2-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:13a3c79816879d9743672a669f1988f74459a78f89a97a2624fae4fe059ffa3a"}, + {file = "pyside6-6.11.2-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:dc6d03990489a5085842770718392ef5de36352d8f608d9a9fe03e60af4d7b66"}, + {file = "pyside6-6.11.2-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:57fe867a6c93821a085d74e8a26f863fc2363516848181c466032595b33b1729"}, + {file = "pyside6-6.11.2-cp310-abi3-win_amd64.whl", hash = "sha256:3201d67e3c10be2eaedd3910ff0f02351eca7e88c95a291cde5e7f2f55ef207f"}, + {file = "pyside6-6.11.2-cp310-abi3-win_arm64.whl", hash = "sha256:0444ac71d0791a19bded35f6d9a94515941f23a6eb7098ca45a1b64a338be697"}, ] [package.dependencies] -PySide6_Addons = "6.11.1" -PySide6_Essentials = "6.11.1" -shiboken6 = "6.11.1" +PySide6_Addons = "6.11.2" +PySide6_Essentials = "6.11.2" +shiboken6 = "6.11.2" tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} [[package]] name = "pyside6-addons" -version = "6.11.1" +version = "6.11.2" description = "Python bindings for the Qt cross-platform application and UI framework (Addons)" optional = false python-versions = "<3.15,>=3.10" +groups = ["main"] files = [ - {file = "pyside6_addons-6.11.1-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:54733c77f789bef5f03c6aff4ad3bec8b2eff021f0cfcbc53d5e6c250ded24f9"}, - {file = "pyside6_addons-6.11.1-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:8e6c65fbd73a512d6f72cda8d8277444a85a34dc99dd1dae9c21d35b8671bb1f"}, - {file = "pyside6_addons-6.11.1-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:bf1c6c4e954e5eba3d2a7c661ad4b9689e8f09c7f4a16bdf29713371d11af993"}, - {file = "pyside6_addons-6.11.1-cp310-abi3-win_amd64.whl", hash = "sha256:0d13c4dfd671b050a48e4f8d8ddc724b7248f9c0437e7fc47fdf316278572923"}, - {file = "pyside6_addons-6.11.1-cp310-abi3-win_arm64.whl", hash = "sha256:3494f480dee92f415be2f2d989c0b3f4755ac332b28045cbf4ba0f5c5a22ba37"}, + {file = "pyside6_addons-6.11.2-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:354c574a839f7d0751960ba03f33bec87c95e0f2796dca3287f54d03abe97dac"}, + {file = "pyside6_addons-6.11.2-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a8b00956925fbdeffc7052cf933506391289f35ac4f3f71a0a167ec195cd47b4"}, + {file = "pyside6_addons-6.11.2-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:a2ca3c73e5f060d4aca49451abd9fba68dd0e1c66ff40cea3a1fe0415def579a"}, + {file = "pyside6_addons-6.11.2-cp310-abi3-win_amd64.whl", hash = "sha256:f449ea4431da20e7b86752cca8d166f93434516fe417f981c27e5f8e1b554407"}, + {file = "pyside6_addons-6.11.2-cp310-abi3-win_arm64.whl", hash = "sha256:d5606af369484b862b4d5741cfc179e19d2b8819c3e04d210b8188e0c9f85988"}, ] [package.dependencies] -PySide6_Essentials = "6.11.1" -shiboken6 = "6.11.1" +PySide6_Essentials = "6.11.2" +shiboken6 = "6.11.2" [[package]] name = "pyside6-essentials" -version = "6.11.1" +version = "6.11.2" description = "Python bindings for the Qt cross-platform application and UI framework (Essentials)" optional = false python-versions = "<3.15,>=3.10" +groups = ["main"] files = [ - {file = "pyside6_essentials-6.11.1-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:228de53c2bc26b07e5021fbe3614fc44ca08e4dab9999af08c2b389d2c239957"}, - {file = "pyside6_essentials-6.11.1-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:e3ef7027b41e4e55fadb56e3b3257dc8ee92154b639fe67fc4c8e05e9d976c60"}, - {file = "pyside6_essentials-6.11.1-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:a039b6da68a3a4b9d243217b2b98d475eed3f617159ef6be925badab53c11b0d"}, - {file = "pyside6_essentials-6.11.1-cp310-abi3-win_amd64.whl", hash = "sha256:63311bd48e32c584599ab04b9ef7c324082374cd2c9fa533f978fb893bb47e40"}, - {file = "pyside6_essentials-6.11.1-cp310-abi3-win_arm64.whl", hash = "sha256:11253ea52aabecefe9febddbbe78b43a824129e3af1cec98431028fba7fa954f"}, + {file = "pyside6_essentials-6.11.2-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:77795c145202e65a78d88f7cd409d186e3ba23d159bdb3ba2dcd159ae5e5f0d9"}, + {file = "pyside6_essentials-6.11.2-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:aaf9f25f0f324874085fa5b26a610318db8a8e243cf85bb3e5400595191c7778"}, + {file = "pyside6_essentials-6.11.2-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:d3ec6e1885c46e57f16364f38ea8040ffc5dc0b18341058f608fede3ba930567"}, + {file = "pyside6_essentials-6.11.2-cp310-abi3-win_amd64.whl", hash = "sha256:c8a29def77032773a30879f7f24415b5395ad08592d147c170824ef4c735dfc1"}, + {file = "pyside6_essentials-6.11.2-cp310-abi3-win_arm64.whl", hash = "sha256:fadd75c5c20800d64dd0a586ea8cb337c5e630aa2246df0e5105738afa46e02c"}, ] [package.dependencies] -shiboken6 = "6.11.1" +shiboken6 = "6.11.2" [[package]] name = "pytest" @@ -480,6 +557,7 @@ version = "8.4.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.9" +groups = ["test"] files = [ {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, @@ -503,6 +581,7 @@ version = "7.1.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.9" +groups = ["test"] files = [ {file = "pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678"}, {file = "pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2"}, @@ -522,6 +601,7 @@ version = "3.15.1" description = "Thin-wrapper around the mock package for easier use with pytest" optional = false python-versions = ">=3.9" +groups = ["test"] files = [ {file = "pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d"}, {file = "pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f"}, @@ -539,6 +619,7 @@ version = "4.5.0" description = "pytest support for PyQt and PySide applications" optional = false python-versions = ">=3.9" +groups = ["test"] files = [ {file = "pytest_qt-4.5.0-py3-none-any.whl", hash = "sha256:ed21ea9b861247f7d18090a26bfbda8fb51d7a8a7b6f776157426ff2ccf26eff"}, {file = "pytest_qt-4.5.0.tar.gz", hash = "sha256:51620e01c488f065d2036425cbc1cbcf8a6972295105fd285321eb47e66a319f"}, @@ -559,6 +640,7 @@ version = "2.34.2" description = "Python HTTP for Humans." optional = false python-versions = ">=3.10" +groups = ["main", "dev"] files = [ {file = "requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0"}, {file = "requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed"}, @@ -576,16 +658,17 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<8)"] [[package]] name = "shiboken6" -version = "6.11.1" +version = "6.11.2" description = "Python/C++ bindings helper module" optional = false python-versions = "<3.15,>=3.10" +groups = ["main"] files = [ - {file = "shiboken6-6.11.1-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:1a16867f103ef1c662a5f09dfed03273a9f81688b174555162c58e83650a3f02"}, - {file = "shiboken6-6.11.1-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9a8bccfafc8805254cabcfa1edfaf55cd52889f4998c91ad0d9a4433fb1bcdbe"}, - {file = "shiboken6-6.11.1-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:1bd2f4314414df2d122d9f646e03b731bc6d6b5f77a5f53f99a4fe4e97d84e6f"}, - {file = "shiboken6-6.11.1-cp310-abi3-win_amd64.whl", hash = "sha256:c2c6863aa80ec18c0f82cea3417837b279cdc60024ac17123461dc9042577df7"}, - {file = "shiboken6-6.11.1-cp310-abi3-win_arm64.whl", hash = "sha256:7c8d9af17db4495d4fa5b1c393f218311c4855546b9dfa6a0bd21bcd66b55e9d"}, + {file = "shiboken6-6.11.2-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:53659683b1f7a08e9f87eff9b1065f1ceb7110cd7a4bc09fdf5efe43d286604d"}, + {file = "shiboken6-6.11.2-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:7a7a0a72a9ed26c9bf77d42246b1c736486befb8f31aa2fb29957ea4cdd1c1c2"}, + {file = "shiboken6-6.11.2-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:4bbbd6fa4d7cff5ec5e12bc4c10e1d845fa30c89457ecb13cc64f7bddb77f6f9"}, + {file = "shiboken6-6.11.2-cp310-abi3-win_amd64.whl", hash = "sha256:6ab0eba1c904455df621f9a6df3ca2bb896bab8670572d2bc4e37804ae91f19a"}, + {file = "shiboken6-6.11.2-cp310-abi3-win_arm64.whl", hash = "sha256:97c49432488df958f0308736f3d15b6915d8826fc380af17bb4b9ec5c4a5e81b"}, ] [[package]] @@ -594,6 +677,7 @@ version = "2.4.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" +groups = ["main", "dev", "test"] files = [ {file = "tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30"}, {file = "tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a"}, @@ -643,16 +727,18 @@ files = [ {file = "tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe"}, {file = "tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f"}, ] +markers = {main = "python_version == \"3.10\"", dev = "python_version == \"3.10\"", test = "python_full_version <= \"3.11.0a6\""} [[package]] name = "typing-extensions" -version = "4.15.0" +version = "4.16.0" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" +groups = ["test"] files = [ - {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, - {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, + {file = "typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8"}, + {file = "typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5"}, ] [[package]] @@ -661,18 +747,19 @@ version = "2.7.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.10" +groups = ["main", "dev"] files = [ {file = "urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897"}, {file = "urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c"}, ] [package.extras] -brotli = ["brotli (>=1.2.0)", "brotlicffi (>=1.2.0.0)"] +brotli = ["brotli (>=1.2.0) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=1.2.0.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["backports-zstd (>=1.0.0)"] +zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = ">=3.10,<3.15" content-hash = "fcec974441087eecefc0adcd76be7f798447ea69b9216296fc347a4e3b6dafba" diff --git a/rpm/bouquin.spec b/rpm/bouquin.spec index 46c8bc6..feb8c89 100644 --- a/rpm/bouquin.spec +++ b/rpm/bouquin.spec @@ -4,7 +4,7 @@ # provides the Python distribution/module as "sqlcipher4". To keep Fedora's # auto-generated python3dist() Requires correct, we rewrite the dependency key in # pyproject.toml at build time. -%global upstream_version 0.9.0 +%global upstream_version 0.10.0 Name: bouquin Version: %{upstream_version} @@ -82,6 +82,9 @@ install -Dpm 0644 bouquin/icons/bouquin.svg %{buildroot}%{_datadir}/icons/hicolo %{_datadir}/icons/hicolor/scalable/apps/bouquin.svg %changelog +* Tue Aug 25 2026 Miguel Jacq - %{version}-%{release} +- New 'Earnings' interface for viewing/tracking earnings across reporting periods (e.g for BAS) +- Dependency updates (including upgrade to SQLCipher 4.18.0) * Sun Jun 07 2026 Miguel Jacq - %{version}-%{release} - Add 'Projects' interface for unified time/invoice/docs view. - Add ability to set a 'bucket' of (prepaid) hours for a project and warn when time logged approaches it. From b2b357a429acd4241de7f4fd53d3f36cc1542f7c Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 25 Aug 2026 17:17:14 +1000 Subject: [PATCH 3/3] 0.10.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e07d5f0..ab26258 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "bouquin" -version = "0.9.0" +version = "0.10.0" description = "Bouquin is a simple, opinionated notebook application written in Python, PyQt and SQLCipher." authors = ["Miguel Jacq "] readme = "README.md"