Compare commits
2
Commits
18456cf8a4
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd171d2384 | ||
|
|
129b1eff04 |
@@ -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):
|
def _filter_compatible(query, battery_size):
|
||||||
"""Restrict a Battery query to size-compatible batteries (matching size or unsized)."""
|
"""Restrict a Battery query to size-compatible batteries (matching size or unsized)."""
|
||||||
if battery_size:
|
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
|
return query
|
||||||
|
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@ def create_app(config_object="config"):
|
|||||||
avail_count = sum(1 for b in batteries if b.status == "available")
|
avail_count = sum(1 for b in batteries if b.status == "available")
|
||||||
installed_count = sum(1 for b in batteries if b.status == "installed")
|
installed_count = sum(1 for b in batteries if b.status == "installed")
|
||||||
retired_count = sum(1 for b in batteries if b.status == "retired")
|
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)
|
total_devices = len(devices)
|
||||||
full_devices = sum(
|
full_devices = sum(
|
||||||
1 for d in devices
|
1 for d in devices
|
||||||
@@ -283,16 +283,16 @@ def create_app(config_object="config"):
|
|||||||
.order_by(BatteryPctLog.recorded_at.desc())
|
.order_by(BatteryPctLog.recorded_at.desc())
|
||||||
.all())
|
.all())
|
||||||
charge_logs_data = [
|
charge_logs_data = [
|
||||||
{"id": l.id, "date": l.charged_date, "cycles": l.increment_cycles, "notes": l.notes or ""}
|
{"id": log.id, "date": log.charged_date, "cycles": log.increment_cycles, "notes": log.notes or ""}
|
||||||
for l in charge_logs
|
for log in charge_logs
|
||||||
]
|
]
|
||||||
capacity_tests_data = [
|
capacity_tests_data = [
|
||||||
{"id": t.id, "date": t.tested_date, "mah": t.tested_capacity_mah, "notes": t.notes or ""}
|
{"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)
|
for t in sorted(capacity_tests, key=lambda t: (t.tested_date, t.id), reverse=True)
|
||||||
]
|
]
|
||||||
pct_logs_data = [
|
pct_logs_data = [
|
||||||
{"recorded_at": str(l.recorded_at), "pct": l.percentage, "source": l.source or ""}
|
{"recorded_at": str(log.recorded_at), "pct": log.percentage, "source": log.source or ""}
|
||||||
for l in pct_logs
|
for log in pct_logs
|
||||||
]
|
]
|
||||||
return render_template("battery_detail.html", battery=battery,
|
return render_template("battery_detail.html", battery=battery,
|
||||||
storage_locations=storage_locations,
|
storage_locations=storage_locations,
|
||||||
@@ -699,7 +699,7 @@ def create_app(config_object="config"):
|
|||||||
|
|
||||||
@app.route("/device/")
|
@app.route("/device/")
|
||||||
def device_list():
|
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_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_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})
|
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()
|
buf = io.StringIO()
|
||||||
w = csv.writer(buf)
|
w = csv.writer(buf)
|
||||||
w.writerow(["id", "battery_id", "battery_label", "charged_date", "increment_cycles", "notes"])
|
w.writerow(["id", "battery_id", "battery_label", "charged_date", "increment_cycles", "notes"])
|
||||||
for l in rows:
|
for row in rows:
|
||||||
w.writerow([l.id, l.battery_id, l.battery.label,
|
w.writerow([row.id, row.battery_id, row.battery.label,
|
||||||
l.charged_date, l.increment_cycles, l.notes or ""])
|
row.charged_date, row.increment_cycles, row.notes or ""])
|
||||||
return buf.getvalue()
|
return buf.getvalue()
|
||||||
|
|
||||||
def _capacity_tests_csv():
|
def _capacity_tests_csv():
|
||||||
@@ -1257,9 +1257,9 @@ def create_app(config_object="config"):
|
|||||||
buf = io.StringIO()
|
buf = io.StringIO()
|
||||||
w = csv.writer(buf)
|
w = csv.writer(buf)
|
||||||
w.writerow(["id", "battery_id", "battery_label", "percentage", "recorded_at", "source"])
|
w.writerow(["id", "battery_id", "battery_label", "percentage", "recorded_at", "source"])
|
||||||
for l in rows:
|
for row in rows:
|
||||||
w.writerow([l.id, l.battery_id, l.battery.label,
|
w.writerow([row.id, row.battery_id, row.battery.label,
|
||||||
l.percentage, l.recorded_at, l.source or ""])
|
row.percentage, row.recorded_at, row.source or ""])
|
||||||
return buf.getvalue()
|
return buf.getvalue()
|
||||||
|
|
||||||
@app.route("/export")
|
@app.route("/export")
|
||||||
@@ -1334,10 +1334,10 @@ def create_app(config_object="config"):
|
|||||||
for d in devices
|
for d in devices
|
||||||
],
|
],
|
||||||
"charge_logs": [
|
"charge_logs": [
|
||||||
{"id": l.id, "battery_id": l.battery_id, "battery_label": l.battery.label,
|
{"id": log.id, "battery_id": log.battery_id, "battery_label": log.battery.label,
|
||||||
"charged_date": l.charged_date, "increment_cycles": l.increment_cycles,
|
"charged_date": log.charged_date, "increment_cycles": log.increment_cycles,
|
||||||
"notes": l.notes}
|
"notes": log.notes}
|
||||||
for l in charge_logs
|
for log in charge_logs
|
||||||
],
|
],
|
||||||
"capacity_tests": [
|
"capacity_tests": [
|
||||||
{"id": t.id, "battery_id": t.battery_id, "battery_label": t.battery.label,
|
{"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
|
for t in capacity_tests
|
||||||
],
|
],
|
||||||
"pct_logs": [
|
"pct_logs": [
|
||||||
{"id": l.id, "battery_id": l.battery_id, "battery_label": l.battery.label,
|
{"id": log.id, "battery_id": log.battery_id, "battery_label": log.battery.label,
|
||||||
"percentage": l.percentage, "recorded_at": l.recorded_at, "source": l.source}
|
"percentage": log.percentage, "recorded_at": log.recorded_at, "source": log.source}
|
||||||
for l in pct_logs
|
for log in pct_logs
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
return Response(json.dumps(payload, indent=2), mimetype="application/json",
|
return Response(json.dumps(payload, indent=2), mimetype="application/json",
|
||||||
|
|||||||
+1
-1
@@ -37,7 +37,7 @@ class HaPoller:
|
|||||||
self._poll_once()
|
self._poll_once()
|
||||||
|
|
||||||
def _poll_once(self):
|
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()
|
session = self._Session()
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from sqlalchemy import Column, Integer, String, Text, ForeignKey, Table, UniqueConstraint
|
from sqlalchemy import Column, Integer, String, Text, ForeignKey, Table, UniqueConstraint
|
||||||
from sqlalchemy.orm import declarative_base, relationship
|
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)."""
|
"""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)):
|
def make_png(size, rgb=(0x25, 0x63, 0xEB)):
|
||||||
"""Create a minimal valid RGB PNG of the given size filled with one color."""
|
"""Create a minimal valid RGB PNG of the given size filled with one color."""
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ Usage:
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|||||||
@@ -31,11 +31,11 @@ from pathlib import Path
|
|||||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||||
sys.path.insert(0, str(REPO_ROOT))
|
sys.path.insert(0, str(REPO_ROOT))
|
||||||
|
|
||||||
from sqlalchemy import create_engine, text
|
from sqlalchemy import create_engine, text # noqa: E402
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker # noqa: E402
|
||||||
|
|
||||||
import config
|
import config # noqa: E402
|
||||||
from models import Base, Battery, BatteryPctLog, CapacityTest, ChargeLog, Device
|
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."""
|
"""Return a MariaDB SQLAlchemy URL, prompting for any missing pieces."""
|
||||||
url = os.environ.get("MARIADB_URL", "").strip()
|
url = os.environ.get("MARIADB_URL", "").strip()
|
||||||
if url:
|
if url:
|
||||||
print(f" Using MARIADB_URL from environment.")
|
print(" Using MARIADB_URL from environment.")
|
||||||
return url
|
return url
|
||||||
|
|
||||||
print("Enter MariaDB connection details (press Enter to accept defaults):\n")
|
print("Enter MariaDB connection details (press Enter to accept defaults):\n")
|
||||||
|
|||||||
@@ -116,6 +116,7 @@
|
|||||||
nav .brand:hover { text-decoration: none; opacity: 0.9; }
|
nav .brand:hover { text-decoration: none; opacity: 0.9; }
|
||||||
nav a { color: #bfdbfe; font-size: 0.9rem; padding: 0.25rem 0.5rem; border-radius: 4px; white-space: nowrap; }
|
nav a { color: #bfdbfe; font-size: 0.9rem; padding: 0.25rem 0.5rem; border-radius: 4px; white-space: nowrap; }
|
||||||
nav a:hover { background: #1d4ed8; color: #fff; text-decoration: none; }
|
nav a:hover { background: #1d4ed8; color: #fff; text-decoration: none; }
|
||||||
|
nav a.active { background: #1d4ed8; color: #fff; }
|
||||||
|
|
||||||
/* Layout */
|
/* Layout */
|
||||||
.container { max-width: 960px; margin: 1.5rem auto; padding: 0 1rem; }
|
.container { max-width: 960px; margin: 1.5rem auto; padding: 0 1rem; }
|
||||||
@@ -196,6 +197,7 @@
|
|||||||
nav { background: #0c1a3b; }
|
nav { background: #0c1a3b; }
|
||||||
nav a { color: #93c5fd; }
|
nav a { color: #93c5fd; }
|
||||||
nav a:hover { background: #1e3a8a; color: #fff; }
|
nav a:hover { background: #1e3a8a; color: #fff; }
|
||||||
|
nav a.active { background: #1e3a8a; color: #fff; }
|
||||||
nav .brand { color: #e2e8f0; }
|
nav .brand { color: #e2e8f0; }
|
||||||
|
|
||||||
#bulk-toolbar { background: var(--bg-toolbar) !important; }
|
#bulk-toolbar { background: var(--bg-toolbar) !important; }
|
||||||
|
|||||||
+5
-4
@@ -17,9 +17,10 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<nav>
|
<nav>
|
||||||
|
{% set ep = request.endpoint or '' %}
|
||||||
<a class="brand" href="{{ url_for('home') }}">Battery Tracker</a>
|
<a class="brand" href="{{ url_for('home') }}">Battery Tracker</a>
|
||||||
<a href="{{ url_for('battery_list') }}">Batteries</a>
|
<a href="{{ url_for('battery_list') }}"{% if ep.startswith('battery') %} class="active"{% endif %}>Batteries</a>
|
||||||
<a href="{{ url_for('device_list') }}">Devices</a>
|
<a href="{{ url_for('device_list') }}"{% if ep.startswith('device') %} class="active"{% endif %}>Devices</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@@ -52,13 +53,13 @@
|
|||||||
</svg>
|
</svg>
|
||||||
<span>Home</span>
|
<span>Home</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ url_for('battery_list') }}" class="bottom-nav-item{% if ep == 'battery_list' %} active{% endif %}">
|
<a href="{{ url_for('battery_list') }}" class="bottom-nav-item{% if ep.startswith('battery') %} active{% endif %}">
|
||||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||||
<rect x="2" y="7" width="16" height="10" rx="2"/><path d="M22 11v2"/>
|
<rect x="2" y="7" width="16" height="10" rx="2"/><path d="M22 11v2"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span>Batteries</span>
|
<span>Batteries</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ url_for('device_list') }}" class="bottom-nav-item{% if ep.startswith('device') and ep != 'device_add' %} active{% endif %}">
|
<a href="{{ url_for('device_list') }}" class="bottom-nav-item{% if ep.startswith('device') %} active{% endif %}">
|
||||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||||
<rect x="5" y="2" width="14" height="20" rx="2"/><line x1="12" y1="18" x2="12.01" y2="18"/>
|
<rect x="5" y="2" width="14" height="20" rx="2"/><line x1="12" y1="18" x2="12.01" y2="18"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
+13
-13
@@ -23,6 +23,19 @@
|
|||||||
style="display:none;margin-top:0.4rem;">
|
style="display:none;margin-top:0.4rem;">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Size</label>
|
||||||
|
<select id="size-select" onchange="metaSelectChanged(this,'size')">
|
||||||
|
<option value="">— none —</option>
|
||||||
|
{% for opt in ['AA','AAA','C','D','9V','18650','21700','14500','26650','CR2032','CR123A'] %}
|
||||||
|
<option value="{{ opt }}">{{ opt }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
<option value="__new__">Other…</option>
|
||||||
|
</select>
|
||||||
|
<input type="text" id="size" name="size" value=""
|
||||||
|
placeholder="Enter size" style="display:none;margin-top:0.4rem;">
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="count">Quantity</label>
|
<label for="count">Quantity</label>
|
||||||
<input type="number" id="count" name="count" value="{{ form_count|default(1) }}" min="1" max="50">
|
<input type="number" id="count" name="count" value="{{ form_count|default(1) }}" min="1" max="50">
|
||||||
@@ -42,19 +55,6 @@
|
|||||||
value="" placeholder="e.g. 2000 — optional">
|
value="" placeholder="e.g. 2000 — optional">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Size</label>
|
|
||||||
<select id="size-select" onchange="metaSelectChanged(this,'size')">
|
|
||||||
<option value="">— none —</option>
|
|
||||||
{% for opt in ['AA','AAA','C','D','9V','18650','21700','14500','26650','CR2032','CR123A'] %}
|
|
||||||
<option value="{{ opt }}">{{ opt }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
<option value="__new__">Other…</option>
|
|
||||||
</select>
|
|
||||||
<input type="text" id="size" name="size" value=""
|
|
||||||
placeholder="Enter size" style="display:none;margin-top:0.4rem;">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Chemistry</label>
|
<label>Chemistry</label>
|
||||||
<select id="chemistry-select" onchange="metaSelectChanged(this,'chemistry')">
|
<select id="chemistry-select" onchange="metaSelectChanged(this,'chemistry')">
|
||||||
|
|||||||
+2
-2
@@ -117,8 +117,8 @@
|
|||||||
<a class="btn btn-secondary" style="text-align:center;" href="{{ url_for('battery_list') }}">View All Batteries</a>
|
<a class="btn btn-secondary" style="text-align:center;" href="{{ url_for('battery_list') }}">View All Batteries</a>
|
||||||
<a class="btn btn-primary" style="text-align:center;" href="{{ url_for('device_add') }}">+ Add Device</a>
|
<a class="btn btn-primary" style="text-align:center;" href="{{ url_for('device_add') }}">+ Add Device</a>
|
||||||
<a class="btn btn-secondary" style="text-align:center;" href="{{ url_for('device_list') }}">View All Devices</a>
|
<a class="btn btn-secondary" style="text-align:center;" href="{{ url_for('device_list') }}">View All Devices</a>
|
||||||
<a class="btn btn-secondary" style="text-align:center;" href="{{ url_for('export_page') }}">Export Data</a>
|
<a class="btn btn-secondary" style="text-align:center;" href="{{ url_for('export_page') }}"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px;" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>Export Data</a>
|
||||||
<a class="btn btn-secondary" style="text-align:center;" href="{{ url_for('import_page') }}">Import Data</a>
|
<a class="btn btn-secondary" style="text-align:center;" href="{{ url_for('import_page') }}"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px;" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>Import Data</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ The `seeded_client` fixture pre-populates:
|
|||||||
BrandX 002 (id=3, retired)
|
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)
|
data = _json.loads(seeded_client.get("/export/all.json").data)
|
||||||
bat = next(b for b in data["batteries"] if b["id"] == 1)
|
bat = next(b for b in data["batteries"] if b["id"] == 1)
|
||||||
assert bat["battery_percentage"] == 100
|
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):
|
def test_assign_battery(seeded_client):
|
||||||
@@ -699,9 +700,6 @@ def test_bulk_install_filters_by_size(client):
|
|||||||
# Import
|
# Import
|
||||||
# ------------------------------------------------------------------ #
|
# ------------------------------------------------------------------ #
|
||||||
|
|
||||||
import io
|
|
||||||
import json as _json
|
|
||||||
|
|
||||||
|
|
||||||
def _make_import_payload(devices=None, batteries=None,
|
def _make_import_payload(devices=None, batteries=None,
|
||||||
charge_logs=None, capacity_tests=None, pct_logs=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 io
|
||||||
import json
|
import json
|
||||||
import pytest
|
|
||||||
from unittest.mock import patch, MagicMock
|
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"])
|
engine = create_engine(ha_app.config["SQLALCHEMY_DATABASE_URI"])
|
||||||
s = sessionmaker(bind=engine)()
|
s = sessionmaker(bind=engine)()
|
||||||
s.get(Battery, 1).battery_percentage = 50
|
s.get(Battery, 1).battery_percentage = 50
|
||||||
s.commit(); s.close()
|
s.commit()
|
||||||
|
s.close()
|
||||||
|
|
||||||
from ha_client import HomeAssistantClient
|
from ha_client import HomeAssistantClient
|
||||||
from ha_poller import HaPoller
|
from ha_poller import HaPoller
|
||||||
|
|||||||
Reference in New Issue
Block a user