feat: initial orchestrator service with FastAPI backend and premium GUI
- FastAPI backend with full Proxmox VE API integration - ISO builder using proxmox-auto-install-assistant - Premium dark-mode SPA frontend with glassmorphism design - VM lifecycle management (create, start, stop, destroy) - Build pipeline tracking with real-time logs - Deployment automation for custom auto-installer ISOs - Production deployment script (setup.sh + systemd) - Comprehensive README with API documentation
This commit is contained in:
@@ -0,0 +1,626 @@
|
||||
from __future__ import annotations
|
||||
|
||||
__all__ = (
|
||||
"accumulate",
|
||||
"batched",
|
||||
"Chain",
|
||||
"combinations",
|
||||
"combinations_with_replacement",
|
||||
"compress",
|
||||
"count",
|
||||
"cycle",
|
||||
"dropwhile",
|
||||
"filterfalse",
|
||||
"groupby",
|
||||
"islice",
|
||||
"pairwise",
|
||||
"permutations",
|
||||
"product",
|
||||
"repeat",
|
||||
"starmap",
|
||||
"tee",
|
||||
"takewhile",
|
||||
"zip_longest",
|
||||
)
|
||||
|
||||
import itertools
|
||||
import operator
|
||||
import sys
|
||||
from collections.abc import (
|
||||
AsyncGenerator,
|
||||
AsyncIterable,
|
||||
AsyncIterator,
|
||||
Awaitable,
|
||||
Callable,
|
||||
Iterable,
|
||||
Iterator,
|
||||
)
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Generic, TypeVar, cast, overload
|
||||
|
||||
from ._core._synchronization import Lock
|
||||
from ._core._tasks import CancelScope
|
||||
from .lowlevel import cancel_shielded_checkpoint, checkpoint, checkpoint_if_cancelled
|
||||
|
||||
T = TypeVar("T")
|
||||
R = TypeVar("R")
|
||||
_tee_end = object()
|
||||
|
||||
|
||||
@dataclass(eq=False)
|
||||
class _IterableAsyncIterator(AsyncIterator[T]):
|
||||
iterator: Iterator[T]
|
||||
|
||||
async def __anext__(self) -> T:
|
||||
await checkpoint_if_cancelled()
|
||||
try:
|
||||
result = next(self.iterator)
|
||||
except StopIteration:
|
||||
await cancel_shielded_checkpoint()
|
||||
raise StopAsyncIteration from None
|
||||
|
||||
await cancel_shielded_checkpoint()
|
||||
return result
|
||||
|
||||
|
||||
def _iterate(iterable: Iterable[T] | AsyncIterable[T]) -> AsyncIterator[T]:
|
||||
if isinstance(iterable, AsyncIterator):
|
||||
return iterable
|
||||
|
||||
if isinstance(iterable, AsyncIterable):
|
||||
return iterable.__aiter__()
|
||||
|
||||
return _IterableAsyncIterator(iter(iterable))
|
||||
|
||||
|
||||
@dataclass(eq=False)
|
||||
class _TeeLink(Generic[T]):
|
||||
value: object | None = None
|
||||
next: _TeeLink[T] | None = None
|
||||
filled: bool = False
|
||||
|
||||
|
||||
@dataclass(eq=False)
|
||||
class _TeeState(Generic[T]):
|
||||
iterator: AsyncIterator[T]
|
||||
lock: Lock = field(default_factory=Lock)
|
||||
|
||||
async def fill(self, link: _TeeLink[T]) -> bool:
|
||||
if link.filled:
|
||||
return False
|
||||
|
||||
async with self.lock:
|
||||
if link.filled:
|
||||
return True
|
||||
|
||||
link.value = await anext(self.iterator, _tee_end)
|
||||
if link.value is not _tee_end:
|
||||
link.next = _TeeLink()
|
||||
|
||||
link.filled = True
|
||||
return True
|
||||
|
||||
|
||||
class _TeeAsyncIterator(AsyncIterator[T]):
|
||||
_state: _TeeState[T]
|
||||
_link: _TeeLink[T]
|
||||
_element_yielded: bool
|
||||
|
||||
def __init__(
|
||||
self, iterable: Iterable[T] | AsyncIterable[T] | _TeeAsyncIterator[T]
|
||||
) -> None:
|
||||
if isinstance(iterable, _TeeAsyncIterator):
|
||||
self._state = iterable._state
|
||||
self._link = iterable._link
|
||||
else:
|
||||
self._state = _TeeState(_iterate(iterable))
|
||||
self._link = _TeeLink()
|
||||
|
||||
self._element_yielded = False
|
||||
|
||||
async def __anext__(self) -> T:
|
||||
had_yieldpoint = await self._state.fill(self._link)
|
||||
if self._link.value is _tee_end:
|
||||
if not self._element_yielded:
|
||||
await checkpoint()
|
||||
|
||||
raise StopAsyncIteration
|
||||
|
||||
if not had_yieldpoint:
|
||||
await checkpoint_if_cancelled()
|
||||
|
||||
self._element_yielded = True
|
||||
value = cast(T, self._link.value)
|
||||
next_link = self._link.next
|
||||
assert next_link is not None
|
||||
self._link = next_link
|
||||
if not had_yieldpoint:
|
||||
await cancel_shielded_checkpoint()
|
||||
|
||||
return value
|
||||
|
||||
|
||||
async def _operator_add(x: T, y: T) -> T:
|
||||
return operator.add(x, y)
|
||||
|
||||
|
||||
async def accumulate(
|
||||
iterable: Iterable[T] | AsyncIterable[T],
|
||||
function: Callable[[T, T], Awaitable[T]] = _operator_add,
|
||||
*,
|
||||
initial: T | None = None,
|
||||
) -> AsyncGenerator[T, None]:
|
||||
iterator = _iterate(iterable)
|
||||
if initial is None:
|
||||
try:
|
||||
total = await anext(iterator)
|
||||
except StopAsyncIteration:
|
||||
await checkpoint()
|
||||
return
|
||||
else:
|
||||
await checkpoint_if_cancelled()
|
||||
total = initial
|
||||
await cancel_shielded_checkpoint()
|
||||
|
||||
yield total
|
||||
|
||||
async for element in iterator:
|
||||
total = await function(total, element)
|
||||
yield total
|
||||
|
||||
|
||||
async def batched(
|
||||
iterable: Iterable[T] | AsyncIterable[T], n: int, *, strict: bool = False
|
||||
) -> AsyncGenerator[tuple[T, ...], None]:
|
||||
if n < 1:
|
||||
raise ValueError("n must be at least one")
|
||||
|
||||
iterator = _iterate(iterable)
|
||||
|
||||
while True:
|
||||
batch: list[T] = []
|
||||
for _ in range(n):
|
||||
try:
|
||||
batch.append(await anext(iterator))
|
||||
except StopAsyncIteration:
|
||||
if not batch:
|
||||
await checkpoint()
|
||||
return
|
||||
if strict:
|
||||
raise ValueError("batched(): incomplete batch") from None
|
||||
|
||||
yield tuple(batch)
|
||||
return
|
||||
|
||||
yield tuple(batch)
|
||||
|
||||
|
||||
class Chain:
|
||||
def __call__(
|
||||
self, *iterables: Iterable[T] | AsyncIterable[T]
|
||||
) -> AsyncGenerator[T, None]:
|
||||
return self.from_iterable(iterables)
|
||||
|
||||
async def from_iterable(
|
||||
self,
|
||||
iterables: (
|
||||
Iterable[Iterable[T] | AsyncIterable[T]]
|
||||
| AsyncIterable[Iterable[T] | AsyncIterable[T]]
|
||||
),
|
||||
) -> AsyncGenerator[T, None]:
|
||||
element_yielded = False
|
||||
outer_iter = _iterate(iterables)
|
||||
|
||||
try:
|
||||
async for iterable in outer_iter:
|
||||
async for element in _iterate(iterable):
|
||||
element_yielded = True
|
||||
yield element
|
||||
finally:
|
||||
aclose = getattr(outer_iter, "aclose", None)
|
||||
if aclose is not None:
|
||||
with CancelScope(shield=True):
|
||||
await aclose()
|
||||
|
||||
if not element_yielded:
|
||||
await checkpoint()
|
||||
|
||||
|
||||
chain: Chain = Chain()
|
||||
|
||||
|
||||
async def combinations(
|
||||
iterable: Iterable[T] | AsyncIterable[T], r: int
|
||||
) -> AsyncGenerator[tuple[T, ...], None]:
|
||||
pool: list[T] = [element async for element in _iterate(iterable)]
|
||||
async for combination in _iterate(itertools.combinations(pool, r)):
|
||||
yield combination
|
||||
|
||||
|
||||
async def combinations_with_replacement(
|
||||
iterable: Iterable[T] | AsyncIterable[T], r: int
|
||||
) -> AsyncGenerator[tuple[T, ...], None]:
|
||||
pool: list[T] = [element async for element in _iterate(iterable)]
|
||||
async for combination in _iterate(itertools.combinations_with_replacement(pool, r)):
|
||||
yield combination
|
||||
|
||||
|
||||
async def compress(
|
||||
data: Iterable[T] | AsyncIterable[T],
|
||||
selectors: Iterable[object] | AsyncIterable[object],
|
||||
) -> AsyncGenerator[T, None]:
|
||||
data_iterator = _iterate(data)
|
||||
selector_iterator = _iterate(selectors)
|
||||
element_yielded = False
|
||||
|
||||
while True:
|
||||
try:
|
||||
datum = await anext(data_iterator)
|
||||
selector = await anext(selector_iterator)
|
||||
except StopAsyncIteration:
|
||||
if not element_yielded:
|
||||
await checkpoint()
|
||||
|
||||
return
|
||||
|
||||
if selector:
|
||||
element_yielded = True
|
||||
yield datum
|
||||
|
||||
|
||||
async def count(start: int = 0, step: int = 1) -> AsyncGenerator[int, None]:
|
||||
n = start
|
||||
while True:
|
||||
await checkpoint_if_cancelled()
|
||||
value = n
|
||||
n += step
|
||||
await cancel_shielded_checkpoint()
|
||||
yield value
|
||||
|
||||
|
||||
async def cycle(
|
||||
iterable: Iterable[T] | AsyncIterable[T],
|
||||
) -> AsyncGenerator[T, None]:
|
||||
saved: list[T] = []
|
||||
async for element in _iterate(iterable):
|
||||
saved.append(element)
|
||||
yield element
|
||||
|
||||
if not saved:
|
||||
await checkpoint()
|
||||
return
|
||||
|
||||
while True:
|
||||
for element in saved:
|
||||
await checkpoint()
|
||||
yield element
|
||||
|
||||
|
||||
async def dropwhile(
|
||||
predicate: Callable[[T], Awaitable[object]],
|
||||
iterable: Iterable[T] | AsyncIterable[T],
|
||||
) -> AsyncGenerator[T, None]:
|
||||
element_yielded = False
|
||||
dropping = True
|
||||
|
||||
async for element in _iterate(iterable):
|
||||
if dropping and await predicate(element):
|
||||
continue
|
||||
|
||||
dropping = False
|
||||
element_yielded = True
|
||||
yield element
|
||||
|
||||
if not element_yielded:
|
||||
await checkpoint()
|
||||
|
||||
|
||||
async def filterfalse(
|
||||
predicate: Callable[[T], Awaitable[object]],
|
||||
iterable: Iterable[T] | AsyncIterable[T],
|
||||
) -> AsyncGenerator[T, None]:
|
||||
element_yielded = False
|
||||
|
||||
async for element in _iterate(iterable):
|
||||
if not await predicate(element):
|
||||
element_yielded = True
|
||||
yield element
|
||||
|
||||
if not element_yielded:
|
||||
await checkpoint()
|
||||
|
||||
|
||||
@overload
|
||||
def groupby(
|
||||
iterable: Iterable[T] | AsyncIterable[T],
|
||||
) -> AsyncGenerator[tuple[T, list[T]], None]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def groupby(
|
||||
iterable: Iterable[T] | AsyncIterable[T],
|
||||
key: Callable[[T], Awaitable[R]],
|
||||
) -> AsyncGenerator[tuple[R, list[T]], None]: ...
|
||||
|
||||
|
||||
async def groupby(
|
||||
iterable: Iterable[T] | AsyncIterable[T],
|
||||
key: Callable[[T], Awaitable[object]] | None = None,
|
||||
) -> AsyncGenerator[tuple[object, list[T]], None]:
|
||||
iterator = _iterate(iterable)
|
||||
try:
|
||||
element = await anext(iterator)
|
||||
except StopAsyncIteration:
|
||||
await checkpoint()
|
||||
return
|
||||
|
||||
group_key = element if key is None else await key(element)
|
||||
values = [element]
|
||||
|
||||
async for element in iterator:
|
||||
next_key = element if key is None else await key(element)
|
||||
if next_key != group_key:
|
||||
completed_group = group_key, values
|
||||
group_key = next_key
|
||||
values = [element]
|
||||
yield completed_group
|
||||
else:
|
||||
values.append(element)
|
||||
|
||||
yield group_key, values
|
||||
|
||||
|
||||
@overload
|
||||
def islice(
|
||||
iterable: Iterable[T] | AsyncIterable[T],
|
||||
stop: int | None,
|
||||
/,
|
||||
) -> AsyncGenerator[T, None]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def islice(
|
||||
iterable: Iterable[T] | AsyncIterable[T],
|
||||
start: int | None,
|
||||
stop: int | None,
|
||||
step: int | None = 1,
|
||||
/,
|
||||
) -> AsyncGenerator[T, None]: ...
|
||||
|
||||
|
||||
async def islice(
|
||||
iterable: Iterable[T] | AsyncIterable[T],
|
||||
*args: int | None,
|
||||
) -> AsyncGenerator[T, None]:
|
||||
if not args:
|
||||
raise TypeError("islice expected at least 2 arguments, got 1")
|
||||
if len(args) > 3:
|
||||
raise TypeError(f"islice expected at most 4 arguments, got {len(args) + 1}")
|
||||
|
||||
slice_args = slice(*args)
|
||||
|
||||
start_message = (
|
||||
"Indices for islice() must be None or an integer: 0 <= x <= sys.maxsize."
|
||||
)
|
||||
stop_message = (
|
||||
"Stop argument for islice() must be None or an integer: 0 <= x <= sys.maxsize."
|
||||
)
|
||||
step_message = "Step for islice() must be a positive integer or None."
|
||||
|
||||
def normalize_index(value: object, message: str) -> int:
|
||||
try:
|
||||
index = operator.index(cast(Any, value))
|
||||
except TypeError:
|
||||
raise ValueError(message) from None
|
||||
|
||||
if index < 0 or index > sys.maxsize:
|
||||
raise ValueError(message)
|
||||
|
||||
return index
|
||||
|
||||
start = (
|
||||
0
|
||||
if slice_args.start is None
|
||||
else normalize_index(slice_args.start, start_message)
|
||||
)
|
||||
stop = (
|
||||
None
|
||||
if slice_args.stop is None
|
||||
else normalize_index(slice_args.stop, stop_message)
|
||||
)
|
||||
step = (
|
||||
1 if slice_args.step is None else normalize_index(slice_args.step, step_message)
|
||||
)
|
||||
|
||||
if step <= 0:
|
||||
raise ValueError(step_message)
|
||||
|
||||
if stop == 0 or start == stop:
|
||||
await checkpoint()
|
||||
return
|
||||
|
||||
iterator = _iterate(iterable)
|
||||
index = 0
|
||||
element_yielded = False
|
||||
|
||||
while stop is None or index < stop:
|
||||
try:
|
||||
element = await anext(iterator)
|
||||
except StopAsyncIteration:
|
||||
if not element_yielded:
|
||||
await checkpoint()
|
||||
|
||||
return
|
||||
|
||||
if index >= start and (index - start) % step == 0:
|
||||
index += 1
|
||||
element_yielded = True
|
||||
yield element
|
||||
else:
|
||||
index += 1
|
||||
|
||||
if not element_yielded:
|
||||
await checkpoint()
|
||||
|
||||
|
||||
async def pairwise(
|
||||
iterable: Iterable[T] | AsyncIterable[T],
|
||||
) -> AsyncGenerator[tuple[T, T], None]:
|
||||
iterator = _iterate(iterable)
|
||||
try:
|
||||
previous = await anext(iterator)
|
||||
except StopAsyncIteration:
|
||||
await checkpoint()
|
||||
return
|
||||
|
||||
element_yielded = False
|
||||
async for element in iterator:
|
||||
element_yielded = True
|
||||
pair = (previous, element)
|
||||
previous = element
|
||||
yield pair
|
||||
|
||||
if not element_yielded:
|
||||
await checkpoint()
|
||||
|
||||
|
||||
async def permutations(
|
||||
iterable: Iterable[T] | AsyncIterable[T], r: int | None = None
|
||||
) -> AsyncGenerator[tuple[T, ...], None]:
|
||||
pool: list[T] = [element async for element in _iterate(iterable)]
|
||||
n = len(pool)
|
||||
if r is None:
|
||||
r = n
|
||||
elif not isinstance(r, int):
|
||||
raise TypeError("Expected int as r")
|
||||
elif r < 0:
|
||||
raise ValueError("r must be non-negative")
|
||||
|
||||
async for permutation in _iterate(itertools.permutations(pool, r)):
|
||||
yield permutation
|
||||
|
||||
|
||||
async def product(
|
||||
*iterables: Iterable[T] | AsyncIterable[T], repeat: int = 1
|
||||
) -> AsyncGenerator[tuple[T, ...], None]:
|
||||
repeat = operator.index(repeat)
|
||||
if repeat < 0:
|
||||
raise ValueError("repeat argument cannot be negative")
|
||||
|
||||
pools: list[tuple[T, ...]] = []
|
||||
for iterable in iterables:
|
||||
pool: list[T] = [element async for element in _iterate(iterable)]
|
||||
pools.append(tuple(pool))
|
||||
|
||||
async for value in _iterate(itertools.product(*pools, repeat=repeat)):
|
||||
yield value
|
||||
|
||||
|
||||
async def repeat(element: T, times: int | None = None) -> AsyncGenerator[T, None]:
|
||||
if times is None:
|
||||
while True:
|
||||
await checkpoint()
|
||||
yield element
|
||||
|
||||
remaining = operator.index(cast(Any, times))
|
||||
if remaining <= 0:
|
||||
await checkpoint()
|
||||
return
|
||||
|
||||
while remaining > 0:
|
||||
await checkpoint_if_cancelled()
|
||||
remaining -= 1
|
||||
await cancel_shielded_checkpoint()
|
||||
yield element
|
||||
|
||||
|
||||
async def starmap(
|
||||
function: Callable[..., Awaitable[R]],
|
||||
iterable: (
|
||||
Iterable[Iterable[object] | AsyncIterable[object]]
|
||||
| AsyncIterable[Iterable[object] | AsyncIterable[object]]
|
||||
),
|
||||
) -> AsyncGenerator[R, None]:
|
||||
result_yielded = False
|
||||
|
||||
async for args_iterable in _iterate(iterable):
|
||||
args = [element async for element in _iterate(args_iterable)]
|
||||
result_yielded = True
|
||||
yield await function(*args)
|
||||
|
||||
if not result_yielded:
|
||||
await checkpoint()
|
||||
|
||||
|
||||
def tee(
|
||||
iterable: Iterable[T] | AsyncIterable[T], n: int = 2
|
||||
) -> tuple[AsyncIterator[T], ...]:
|
||||
n = operator.index(cast(Any, n))
|
||||
if n < 0:
|
||||
raise ValueError("n must be >= 0")
|
||||
if n == 0:
|
||||
return ()
|
||||
|
||||
iterator = _TeeAsyncIterator(iterable)
|
||||
iterators: list[AsyncIterator[T]] = [iterator]
|
||||
iterators.extend(_TeeAsyncIterator(iterator) for _ in range(n - 1))
|
||||
return tuple(iterators)
|
||||
|
||||
|
||||
async def takewhile(
|
||||
predicate: Callable[[T], Awaitable[object]],
|
||||
iterable: Iterable[T] | AsyncIterable[T],
|
||||
) -> AsyncGenerator[T, None]:
|
||||
element_yielded = False
|
||||
|
||||
async for element in _iterate(iterable):
|
||||
if not await predicate(element):
|
||||
if not element_yielded:
|
||||
await checkpoint()
|
||||
|
||||
return
|
||||
|
||||
element_yielded = True
|
||||
yield element
|
||||
|
||||
if not element_yielded:
|
||||
await checkpoint()
|
||||
|
||||
|
||||
async def zip_longest(
|
||||
*iterables: Iterable[object] | AsyncIterable[object],
|
||||
fillvalue: object = None,
|
||||
) -> AsyncGenerator[tuple[object, ...], None]:
|
||||
iterators = [_iterate(iterable) for iterable in iterables]
|
||||
num_active = len(iterators)
|
||||
if not num_active:
|
||||
await checkpoint()
|
||||
return
|
||||
|
||||
active = [True] * num_active
|
||||
tuple_yielded = False
|
||||
|
||||
while True:
|
||||
values: list[object] = []
|
||||
for index, iterator in enumerate(iterators):
|
||||
if not active[index]:
|
||||
values.append(fillvalue)
|
||||
continue
|
||||
|
||||
try:
|
||||
value = await anext(iterator)
|
||||
except StopAsyncIteration:
|
||||
active[index] = False
|
||||
num_active -= 1
|
||||
if not num_active:
|
||||
if not tuple_yielded:
|
||||
await checkpoint()
|
||||
|
||||
return
|
||||
|
||||
value = fillvalue
|
||||
|
||||
values.append(value)
|
||||
|
||||
tuple_yielded = True
|
||||
yield tuple(values)
|
||||
Reference in New Issue
Block a user