Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/core-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:
with:
version: "latest"
- name: Set up Python
run: uv python install 3.13
run: uv python install 3.14
# Only runtime + build deps: dev-group packages must never influence what
# PyInstaller bundles (see logging.config regression in v4.3.0).
- name: Install Python dependencies
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pack-electron.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ jobs:
with:
version: "latest"
- name: Set up Python
run: uv python install 3.13
run: uv python install 3.14
# Only runtime + build deps: dev-group packages must never influence what
# PyInstaller bundles (see logging.config regression in v4.3.0).
- name: Install Python dependencies
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.13", "3.14"]
python-version: ["3.14"]

steps:
- uses: actions/checkout@v6
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ repos:
exclude: \.po$
- id: trailing-whitespace
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.12.0
rev: v0.16.2
hooks:
- id: ruff
args: [--fix]
Expand Down
5 changes: 2 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ version = "5.2.0"
description = "Painless business planning for freelancers."
readme = "README.md"
license = "GPL-3.0-only"
requires-python = ">=3.13"
requires-python = ">=3.14"
authors = [
{ name = "Christian Staudt" },
{ name = "Vladimir Peter" },
Expand All @@ -16,7 +16,6 @@ classifiers = [
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Natural Language :: English",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
]

Expand Down Expand Up @@ -84,7 +83,7 @@ markers = [
]

[tool.ruff]
target-version = "py313"
target-version = "py314"
line-length = 127
# Python fenced code blocks in docs (.ai/rules/, migrations/README.md, etc.) are
# illustrative snippets, not standalone modules — they reference names like `op`
Expand Down
4 changes: 2 additions & 2 deletions tuttle/app/contracts/intent.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ def _parse_amount(value) -> Decimal:
"""
try:
return Decimal(str(value))
except (InvalidOperation, TypeError, ValueError):
except InvalidOperation, TypeError, ValueError:
raise ValueError(f"Additional charge amount must be a number, got {value!r}")


def _parse_milestone_number(value, field: str) -> Decimal:
"""Coerce a milestone percentage or amount from JSON into a Decimal."""
try:
parsed = Decimal(str(value))
except (InvalidOperation, TypeError, ValueError):
except InvalidOperation, TypeError, ValueError:
raise ValueError(f"Milestone {field} must be a number, got {value!r}")
if parsed <= 0:
raise ValueError(f"Milestone {field} must be greater than zero, got {value!r}")
Expand Down
2 changes: 1 addition & 1 deletion tuttle/app/imports/intent.py
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ def _model_fields(data: dict, model_cls: type) -> dict:
if field_info and field_info.annotation is not None:
try:
v = _coerce_value(v, field_info.annotation)
except (ValueError, ArithmeticError):
except ValueError, ArithmeticError:
raise ValueError(f"the {_field_label(k)} {v!r} is not valid") from None
if v is not None:
result[k] = v
Expand Down
2 changes: 1 addition & 1 deletion tuttle/app/invoicing/data_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ def generate_invoice_number(self, date: datetime.date, scheme: str = "daily") ->
try:
seq = int(inv.number.rsplit("-", 1)[-1]) if prefix else int(inv.number)
max_seq = max(max_seq, seq)
except (ValueError, IndexError):
except ValueError, IndexError:
continue

next_seq = max_seq + 1
Expand Down
2 changes: 1 addition & 1 deletion tuttle/app/invoicing/intent.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def _as_date(value, what: str) -> date:
return value
try:
return _dt.date.fromisoformat(value)
except (TypeError, ValueError):
except TypeError, ValueError:
raise ValueError(f"Enter a valid {what}.") from None


Expand Down
2 changes: 1 addition & 1 deletion tuttle/app/tasks/intent.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ def _same(a: TaskRule, b: TaskRule, fields) -> bool:
def _int(value, message: str) -> int:
try:
return int(value)
except (TypeError, ValueError):
except TypeError, ValueError:
raise ValueError(message) from None


Expand Down
10 changes: 5 additions & 5 deletions tuttle/app/timetracking/intent.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def _validate_entry(tag: str, date: str, start_time, end_time, duration_minutes=
return (*_validate_amount(date, duration_minutes), True)
try:
begin, end = _parse_local(date, start_time), _parse_local(date, end_time)
except (TypeError, ValueError):
except TypeError, ValueError:
raise _EntryError("Enter the date as YYYY-MM-DD and the times as HH:MM.")
if end <= begin:
raise _EntryError("The end time must be after the start time.")
Expand All @@ -74,11 +74,11 @@ def _validate_entry(tag: str, date: str, start_time, end_time, duration_minutes=
def _validate_amount(date: str, duration_minutes):
try:
begin = _parse_local(date, "00:00")
except (TypeError, ValueError):
except TypeError, ValueError:
raise _EntryError("Enter the date as YYYY-MM-DD.")
try:
minutes = round(float(duration_minutes))
except (TypeError, ValueError, OverflowError):
except TypeError, ValueError, OverflowError:
minutes = 0
if not 0 < minutes <= _MAX_DAY_MINUTES:
raise _EntryError("Enter a duration between 1 minute and 24 hours, for example 1h 30m.")
Expand Down Expand Up @@ -120,7 +120,7 @@ def _fetch_system_calendar(calendar_id, from_date, to_date) -> tuple[Optional[Da
def _coerce_id(value) -> Optional[int]:
try:
return int(value)
except (TypeError, ValueError):
except TypeError, ValueError:
return None


Expand Down Expand Up @@ -423,7 +423,7 @@ def stop_timer(
begin = to_cet(state["start_time"])
try:
end = to_cet(end_time) if end_time else to_cet(datetime.datetime.now(tz=datetime.timezone.utc))
except (ValueError, TypeError):
except ValueError, TypeError:
return IntentResult(
was_intent_successful=False,
error_msg="The end time could not be read. Stop the timer again.",
Expand Down
2 changes: 1 addition & 1 deletion tuttle/eventkit_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,7 @@ def fetch_events(
try:
begin_dt = datetime.datetime.fromisoformat(ev["begin"])
end_dt = datetime.datetime.fromisoformat(ev["end"])
except (KeyError, ValueError):
except KeyError, ValueError:
continue

title = ev.get("title", "")
Expand Down
2 changes: 1 addition & 1 deletion tuttle/fx.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ def fx_haircut() -> Decimal:
raw = AppDatabase().get_setting("currency.fx_haircut")
try:
return Decimal(raw) if raw else Decimal("1.0")
except (ArithmeticError, ValueError):
except ArithmeticError, ValueError:
return Decimal("1.0")


Expand Down
2 changes: 1 addition & 1 deletion tuttle/i18n.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def _fill(text: str, source: str, values: dict) -> str:
return text
try:
return text.format(**values)
except (KeyError, IndexError, ValueError):
except KeyError, IndexError, ValueError:
# A translation with a broken placeholder must not break the document.
return source.format(**values)

Expand Down
2 changes: 1 addition & 1 deletion tuttle/mail.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def _ps_escape(s: str) -> str:
timeout=15,
)
return result.returncode == 0
except (FileNotFoundError, subprocess.TimeoutExpired):
except FileNotFoundError, subprocess.TimeoutExpired:
return False


Expand Down
2 changes: 1 addition & 1 deletion tuttle/rendering.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def unit_label(raw_unit, quantity=None) -> str:
"""
try:
count = 1 if quantity is None else float(round_quantity(quantity))
except (TypeError, ValueError, ArithmeticError):
except TypeError, ValueError, ArithmeticError:
count = 1
unit = raw_unit.lower().replace(" ", "_").rstrip("s")
if unit == "hour":
Expand Down
2 changes: 1 addition & 1 deletion tuttle_tests/test_migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ def op_calls(source: str) -> list[str]:
check=True,
capture_output=True,
)
except (FileNotFoundError, subprocess.CalledProcessError):
except FileNotFoundError, subprocess.CalledProcessError:
pytest.skip("Not a git checkout; cannot enforce append-only.")

# c3d70beffa72 was edited after shipping (commit 9cec3e0 changed its
Expand Down
Loading
Loading