This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.13"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pip install ruff
|
||||
pip install -r requirements.txt
|
||||
|
||||
- name: Lint
|
||||
run: ruff check .
|
||||
|
||||
- name: Run tests
|
||||
run: pytest tests/ -v
|
||||
@@ -35,7 +35,7 @@ def _safe_next(default_url):
|
||||
def _filter_compatible(query, battery_size):
|
||||
"""Restrict a Battery query to size-compatible batteries (matching size or unsized)."""
|
||||
if battery_size:
|
||||
query = query.filter((Battery.size == battery_size) | (Battery.size == None))
|
||||
query = query.filter((Battery.size == battery_size) | (Battery.size == None)) # noqa: E711
|
||||
return query
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ def create_app(config_object="config"):
|
||||
avail_count = sum(1 for b in batteries if b.status == "available")
|
||||
installed_count = sum(1 for b in batteries if b.status == "installed")
|
||||
retired_count = sum(1 for b in batteries if b.status == "retired")
|
||||
devices = db.query(Device).filter(Device.parent_id == None).all()
|
||||
devices = db.query(Device).filter(Device.parent_id == None).all() # noqa: E711
|
||||
total_devices = len(devices)
|
||||
full_devices = sum(
|
||||
1 for d in devices
|
||||
@@ -283,16 +283,16 @@ def create_app(config_object="config"):
|
||||
.order_by(BatteryPctLog.recorded_at.desc())
|
||||
.all())
|
||||
charge_logs_data = [
|
||||
{"id": l.id, "date": l.charged_date, "cycles": l.increment_cycles, "notes": l.notes or ""}
|
||||
for l in charge_logs
|
||||
{"id": log.id, "date": log.charged_date, "cycles": log.increment_cycles, "notes": log.notes or ""}
|
||||
for log in charge_logs
|
||||
]
|
||||
capacity_tests_data = [
|
||||
{"id": t.id, "date": t.tested_date, "mah": t.tested_capacity_mah, "notes": t.notes or ""}
|
||||
for t in sorted(capacity_tests, key=lambda t: (t.tested_date, t.id), reverse=True)
|
||||
]
|
||||
pct_logs_data = [
|
||||
{"recorded_at": str(l.recorded_at), "pct": l.percentage, "source": l.source or ""}
|
||||
for l in pct_logs
|
||||
{"recorded_at": str(log.recorded_at), "pct": log.percentage, "source": log.source or ""}
|
||||
for log in pct_logs
|
||||
]
|
||||
return render_template("battery_detail.html", battery=battery,
|
||||
storage_locations=storage_locations,
|
||||
@@ -699,7 +699,7 @@ def create_app(config_object="config"):
|
||||
|
||||
@app.route("/device/")
|
||||
def device_list():
|
||||
devices = db.query(Device).filter(Device.parent_id == None).order_by(Device.name).all()
|
||||
devices = db.query(Device).filter(Device.parent_id == None).order_by(Device.name).all() # noqa: E711
|
||||
device_types = sorted({d.device_type for d in devices if d.device_type})
|
||||
device_locations = sorted({d.location for d in devices if d.location})
|
||||
device_battery_sizes = sorted({d.battery_size for d in devices if d.battery_size})
|
||||
@@ -1237,9 +1237,9 @@ def create_app(config_object="config"):
|
||||
buf = io.StringIO()
|
||||
w = csv.writer(buf)
|
||||
w.writerow(["id", "battery_id", "battery_label", "charged_date", "increment_cycles", "notes"])
|
||||
for l in rows:
|
||||
w.writerow([l.id, l.battery_id, l.battery.label,
|
||||
l.charged_date, l.increment_cycles, l.notes or ""])
|
||||
for row in rows:
|
||||
w.writerow([row.id, row.battery_id, row.battery.label,
|
||||
row.charged_date, row.increment_cycles, row.notes or ""])
|
||||
return buf.getvalue()
|
||||
|
||||
def _capacity_tests_csv():
|
||||
@@ -1257,9 +1257,9 @@ def create_app(config_object="config"):
|
||||
buf = io.StringIO()
|
||||
w = csv.writer(buf)
|
||||
w.writerow(["id", "battery_id", "battery_label", "percentage", "recorded_at", "source"])
|
||||
for l in rows:
|
||||
w.writerow([l.id, l.battery_id, l.battery.label,
|
||||
l.percentage, l.recorded_at, l.source or ""])
|
||||
for row in rows:
|
||||
w.writerow([row.id, row.battery_id, row.battery.label,
|
||||
row.percentage, row.recorded_at, row.source or ""])
|
||||
return buf.getvalue()
|
||||
|
||||
@app.route("/export")
|
||||
@@ -1334,10 +1334,10 @@ def create_app(config_object="config"):
|
||||
for d in devices
|
||||
],
|
||||
"charge_logs": [
|
||||
{"id": l.id, "battery_id": l.battery_id, "battery_label": l.battery.label,
|
||||
"charged_date": l.charged_date, "increment_cycles": l.increment_cycles,
|
||||
"notes": l.notes}
|
||||
for l in charge_logs
|
||||
{"id": log.id, "battery_id": log.battery_id, "battery_label": log.battery.label,
|
||||
"charged_date": log.charged_date, "increment_cycles": log.increment_cycles,
|
||||
"notes": log.notes}
|
||||
for log in charge_logs
|
||||
],
|
||||
"capacity_tests": [
|
||||
{"id": t.id, "battery_id": t.battery_id, "battery_label": t.battery.label,
|
||||
@@ -1346,9 +1346,9 @@ def create_app(config_object="config"):
|
||||
for t in capacity_tests
|
||||
],
|
||||
"pct_logs": [
|
||||
{"id": l.id, "battery_id": l.battery_id, "battery_label": l.battery.label,
|
||||
"percentage": l.percentage, "recorded_at": l.recorded_at, "source": l.source}
|
||||
for l in pct_logs
|
||||
{"id": log.id, "battery_id": log.battery_id, "battery_label": log.battery.label,
|
||||
"percentage": log.percentage, "recorded_at": log.recorded_at, "source": log.source}
|
||||
for log in pct_logs
|
||||
],
|
||||
}
|
||||
return Response(json.dumps(payload, indent=2), mimetype="application/json",
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ class HaPoller:
|
||||
self._poll_once()
|
||||
|
||||
def _poll_once(self):
|
||||
from models import Battery, BatteryPctLog, Device # local import avoids circular-import risk
|
||||
from models import BatteryPctLog, Device # local import avoids circular-import risk
|
||||
|
||||
session = self._Session()
|
||||
try:
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import Column, Integer, String, Text, ForeignKey, Table, UniqueConstraint
|
||||
from sqlalchemy.orm import declarative_base, relationship
|
||||
|
||||
+3
-1
@@ -1,5 +1,7 @@
|
||||
"""Generate solid-color PNG icons for PWA manifest using stdlib only (no Pillow)."""
|
||||
import zlib, struct, os
|
||||
import zlib
|
||||
import struct
|
||||
import os
|
||||
|
||||
def make_png(size, rgb=(0x25, 0x63, 0xEB)):
|
||||
"""Create a minimal valid RGB PNG of the given size filled with one color."""
|
||||
|
||||
@@ -16,7 +16,6 @@ Usage:
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@@ -31,11 +31,11 @@ from pathlib import Path
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(REPO_ROOT))
|
||||
|
||||
from sqlalchemy import create_engine, text
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy import create_engine, text # noqa: E402
|
||||
from sqlalchemy.orm import sessionmaker # noqa: E402
|
||||
|
||||
import config
|
||||
from models import Base, Battery, BatteryPctLog, CapacityTest, ChargeLog, Device
|
||||
import config # noqa: E402
|
||||
from models import Base, Battery, BatteryPctLog, CapacityTest, ChargeLog, Device # noqa: E402
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -62,7 +62,7 @@ def collect_credentials() -> str:
|
||||
"""Return a MariaDB SQLAlchemy URL, prompting for any missing pieces."""
|
||||
url = os.environ.get("MARIADB_URL", "").strip()
|
||||
if url:
|
||||
print(f" Using MARIADB_URL from environment.")
|
||||
print(" Using MARIADB_URL from environment.")
|
||||
return url
|
||||
|
||||
print("Enter MariaDB connection details (press Enter to accept defaults):\n")
|
||||
|
||||
@@ -8,7 +8,8 @@ The `seeded_client` fixture pre-populates:
|
||||
BrandX 002 (id=3, retired)
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import io
|
||||
import json as _json
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
@@ -169,7 +170,7 @@ def test_edit_details_percentage_valid(seeded_client):
|
||||
data = _json.loads(seeded_client.get("/export/all.json").data)
|
||||
bat = next(b for b in data["batteries"] if b["id"] == 1)
|
||||
assert bat["battery_percentage"] == 100
|
||||
assert any(l["battery_id"] == 1 and l["source"] == "manual" for l in data["pct_logs"])
|
||||
assert any(entry["battery_id"] == 1 and entry["source"] == "manual" for entry in data["pct_logs"])
|
||||
|
||||
|
||||
def test_assign_battery(seeded_client):
|
||||
@@ -699,9 +700,6 @@ def test_bulk_install_filters_by_size(client):
|
||||
# Import
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
import io
|
||||
import json as _json
|
||||
|
||||
|
||||
def _make_import_payload(devices=None, batteries=None,
|
||||
charge_logs=None, capacity_tests=None, pct_logs=None):
|
||||
|
||||
@@ -6,7 +6,6 @@ ha_client/ha_poller branches, export/import edge cases, bulk actions.
|
||||
|
||||
import io
|
||||
import json
|
||||
import pytest
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
|
||||
|
||||
@@ -484,7 +484,8 @@ def test_poll_skips_update_when_percentage_unchanged(ha_app, ha_client_f):
|
||||
engine = create_engine(ha_app.config["SQLALCHEMY_DATABASE_URI"])
|
||||
s = sessionmaker(bind=engine)()
|
||||
s.get(Battery, 1).battery_percentage = 50
|
||||
s.commit(); s.close()
|
||||
s.commit()
|
||||
s.close()
|
||||
|
||||
from ha_client import HomeAssistantClient
|
||||
from ha_poller import HaPoller
|
||||
|
||||
Reference in New Issue
Block a user