Задача 042: свёртка свободных адресов в раскрывающиеся диапазоны

В списке адресов префикса подряд идущие свободные адреса сворачиваются в строку
«начало – конец · Свободно: N» (параметр group_free, группировка на сервере — пагинация
по строкам после свёртки); раскрытие — существующим status=free&offset=free_offset
порциями по 500. Без group_free ответ API прежний.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ayurishchevandClaude Opus 5.5 committed 2026-09-27 19:40:22 +03:00
1 parent 9301069c29
commit d1a2efd683
7 files changed
+176 -12

No files matched your search

+33 -2
View File
@@ -380,11 +380,13 @@ def _check_device(db: Session, user: User, prefix: Prefix, device_id: int | None
@router.get("/prefixes/{id}/addresses", response_model=s.AddressPage)
def list_addresses(
id: int, status: str = "", q: str = "", limit: int = Query(100, ge=1, le=500), offset: int = Query(0, ge=0, le=MAX_OFFSET),
id: int, status: str = "", q: str = "", group_free: bool = False, limit: int = Query(100, ge=1, le=500), offset: int = Query(0, ge=0, le=MAX_OFFSET),
db: Session = Depends(get_db), user: User = Depends(current_user), # изменение 032
):
"""status: assigned | reserved | deprecated | free | пусто (все; свободные подмешиваются для малых подсетей).
Пагинация — в SQL; страница «свободных» считается арифметически (без перебора адресов подсети)."""
Пагинация — в SQL; страница «свободных» считается арифметически (без перебора адресов подсети).
group_free (изменение 042): только в смешанном режиме — подряд идущие свободные адреса сворачиваются
в строки free_range; без параметра поведение и ответ побайтно прежние (совместимость, тесты)."""
p = get_or_404(db, Prefix, id, "Префикс")
require_org(user, p.organization_id, "Префикс") # изменение 032
cap = capacity(str(p.prefix))
@@ -404,6 +406,33 @@ def list_addresses(
def free_rows(ips: list[str]) -> list[s.AddressOut]:
return [s.AddressOut(id=None, prefix_id=id, address=ip, status="free") for ip in ips]
def group_free_rows(rows: list[s.AddressOut]) -> list[s.AddressOut]:
"""Изменение 042: подряд идущие строки status=free (серия ≥ 2) сворачиваются в одну строку free_range.
Свёртка — арифметически по уже отсортированному списку (free-строки идут подряд после сортировки),
без повторного перебора подсети. free_offset — число свободных адресов подсети до начала диапазона,
совпадает с offset для GET .../addresses?status=free&offset=…&limit=… (free_page считает так же)."""
out: list[s.AddressOut] = []
free_before, i, n = 0, 0, len(rows)
while i < n:
if rows[i].status != "free":
out.append(rows[i])
i += 1
continue
j = i
while j < n and rows[j].status == "free":
j += 1
run = j - i
if run >= 2:
out.append(s.AddressOut(
id=None, prefix_id=id, address=rows[i].address, status="free_range",
range_end=rows[j - 1].address, range_count=run, free_offset=free_before,
))
else:
out.append(rows[i])
free_before += run
i = j
return out
if status == "free":
return s.AddressPage(items=free_rows(free_page(net, occupied(), offset, limit)), total=summary.free, summary=summary)
@@ -423,6 +452,8 @@ def list_addresses(
if mixed:
rows = [_addr_out(a, dn, dtn, ds.value if ds else None) for a, dn, dtn, ds in db.execute(stmt.order_by(Address.address)).all()]
rows = sorted(rows + free_rows(free_page(net, occupied(), 0, cap)), key=lambda r: ipaddress.ip_address(r.address))
if group_free: # изменение 042
rows = group_free_rows(rows)
return s.AddressPage(items=rows[offset:offset + limit], total=len(rows), summary=summary)
total = count(db, select(Address.id).where(*flt))
page = db.execute(stmt.order_by(Address.address).limit(limit).offset(offset)).all()