Erster Stand der Hintergrundprozesse, die auf der Synology unter /volume1/homes/wagner/SolarManager laufen: der Manager selbst, die Sammler je Geraet, die MQTT-Bruecke, der Wecker und - neu hinzugezogen - der AutoAction-Runner, der als Hintergrundprozess hierher gehoert und nicht ins Web-Verzeichnis. Zugangsdaten stehen nicht mehr im Quelltext, sondern in config.ini, die nicht mit eingecheckt wird. Vorlage ist config.ini.example, gelesen wird sie von konfig.py. Betroffen waren solarManager.py (Datenbank und Wattpilot), zeit.py, gatherWaterData.py, wecker.py und skoda_testdaten.py, das sich das Passwort bisher aus dem Quelltext eines anderen Moduls herausgesucht hat. Die Kia-Anbindung ist mit dem Fahrzeug entfallen: kiaTest.py, gatherCarData.py und hyundai_kia_connect_api sind nicht mehr dabei, ebenso gatherInverterData.py, auf das nur noch eine auskommentierte Zeile zeigte. Die mitgelieferten Bibliotheken bleiben im Repository - die NAS hat kein pip, sie muessen neben den Skripten liegen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
215 lines
5.5 KiB
Python
215 lines
5.5 KiB
Python
import logging
|
|
from abc import ABC, abstractmethod
|
|
from collections.abc import Sized
|
|
from http.cookies import BaseCookie, Morsel
|
|
from typing import (
|
|
TYPE_CHECKING,
|
|
Any,
|
|
Awaitable,
|
|
Callable,
|
|
Dict,
|
|
Generator,
|
|
Iterable,
|
|
List,
|
|
Optional,
|
|
Tuple,
|
|
)
|
|
|
|
from multidict import CIMultiDict
|
|
from yarl import URL
|
|
|
|
from .typedefs import LooseCookies
|
|
|
|
if TYPE_CHECKING: # pragma: no cover
|
|
from .web_app import Application
|
|
from .web_exceptions import HTTPException
|
|
from .web_request import BaseRequest, Request
|
|
from .web_response import StreamResponse
|
|
else:
|
|
BaseRequest = Request = Application = StreamResponse = None
|
|
HTTPException = None
|
|
|
|
|
|
class AbstractRouter(ABC):
|
|
def __init__(self) -> None:
|
|
self._frozen = False
|
|
|
|
def post_init(self, app: Application) -> None:
|
|
"""Post init stage.
|
|
|
|
Not an abstract method for sake of backward compatibility,
|
|
but if the router wants to be aware of the application
|
|
it can override this.
|
|
"""
|
|
|
|
@property
|
|
def frozen(self) -> bool:
|
|
return self._frozen
|
|
|
|
def freeze(self) -> None:
|
|
"""Freeze router."""
|
|
self._frozen = True
|
|
|
|
@abstractmethod
|
|
async def resolve(self, request: Request) -> "AbstractMatchInfo":
|
|
"""Return MATCH_INFO for given request"""
|
|
|
|
|
|
class AbstractMatchInfo(ABC):
|
|
@property # pragma: no branch
|
|
@abstractmethod
|
|
def handler(self) -> Callable[[Request], Awaitable[StreamResponse]]:
|
|
"""Execute matched request handler"""
|
|
|
|
@property
|
|
@abstractmethod
|
|
def expect_handler(
|
|
self,
|
|
) -> Callable[[Request], Awaitable[Optional[StreamResponse]]]:
|
|
"""Expect handler for 100-continue processing"""
|
|
|
|
@property # pragma: no branch
|
|
@abstractmethod
|
|
def http_exception(self) -> Optional[HTTPException]:
|
|
"""HTTPException instance raised on router's resolving, or None"""
|
|
|
|
@abstractmethod # pragma: no branch
|
|
def get_info(self) -> Dict[str, Any]:
|
|
"""Return a dict with additional info useful for introspection"""
|
|
|
|
@property # pragma: no branch
|
|
@abstractmethod
|
|
def apps(self) -> Tuple[Application, ...]:
|
|
"""Stack of nested applications.
|
|
|
|
Top level application is left-most element.
|
|
|
|
"""
|
|
|
|
@abstractmethod
|
|
def add_app(self, app: Application) -> None:
|
|
"""Add application to the nested apps stack."""
|
|
|
|
@abstractmethod
|
|
def freeze(self) -> None:
|
|
"""Freeze the match info.
|
|
|
|
The method is called after route resolution.
|
|
|
|
After the call .add_app() is forbidden.
|
|
|
|
"""
|
|
|
|
|
|
class AbstractView(ABC):
|
|
"""Abstract class based view."""
|
|
|
|
def __init__(self, request: Request) -> None:
|
|
self._request = request
|
|
|
|
@property
|
|
def request(self) -> Request:
|
|
"""Request instance."""
|
|
return self._request
|
|
|
|
@abstractmethod
|
|
def __await__(self) -> Generator[Any, None, StreamResponse]:
|
|
"""Execute the view handler."""
|
|
|
|
|
|
class AbstractResolver(ABC):
|
|
"""Abstract DNS resolver."""
|
|
|
|
@abstractmethod
|
|
async def resolve(self, host: str, port: int, family: int) -> List[Dict[str, Any]]:
|
|
"""Return IP address for given hostname"""
|
|
|
|
@abstractmethod
|
|
async def close(self) -> None:
|
|
"""Release resolver"""
|
|
|
|
|
|
if TYPE_CHECKING: # pragma: no cover
|
|
IterableBase = Iterable[Morsel[str]]
|
|
else:
|
|
IterableBase = Iterable
|
|
|
|
|
|
ClearCookiePredicate = Callable[["Morsel[str]"], bool]
|
|
|
|
|
|
class AbstractCookieJar(Sized, IterableBase):
|
|
"""Abstract Cookie Jar."""
|
|
|
|
@abstractmethod
|
|
def clear(self, predicate: Optional[ClearCookiePredicate] = None) -> None:
|
|
"""Clear all cookies if no predicate is passed."""
|
|
|
|
@abstractmethod
|
|
def clear_domain(self, domain: str) -> None:
|
|
"""Clear all cookies for domain and all subdomains."""
|
|
|
|
@abstractmethod
|
|
def update_cookies(self, cookies: LooseCookies, response_url: URL = URL()) -> None:
|
|
"""Update cookies."""
|
|
|
|
@abstractmethod
|
|
def filter_cookies(self, request_url: URL) -> "BaseCookie[str]":
|
|
"""Return the jar's cookies filtered by their attributes."""
|
|
|
|
|
|
class AbstractStreamWriter(ABC):
|
|
"""Abstract stream writer."""
|
|
|
|
buffer_size = 0
|
|
output_size = 0
|
|
length: Optional[int] = 0
|
|
|
|
@abstractmethod
|
|
async def write(self, chunk: bytes) -> None:
|
|
"""Write chunk into stream."""
|
|
|
|
@abstractmethod
|
|
async def write_eof(self, chunk: bytes = b"") -> None:
|
|
"""Write last chunk."""
|
|
|
|
@abstractmethod
|
|
async def drain(self) -> None:
|
|
"""Flush the write buffer."""
|
|
|
|
@abstractmethod
|
|
def enable_compression(self, encoding: str = "deflate") -> None:
|
|
"""Enable HTTP body compression"""
|
|
|
|
@abstractmethod
|
|
def enable_chunking(self) -> None:
|
|
"""Enable HTTP chunked mode"""
|
|
|
|
@abstractmethod
|
|
async def write_headers(
|
|
self, status_line: str, headers: "CIMultiDict[str]"
|
|
) -> None:
|
|
"""Write HTTP headers"""
|
|
|
|
|
|
class AbstractAccessLogger(ABC):
|
|
"""Abstract writer to access log."""
|
|
|
|
def __init__(self, logger: logging.Logger, log_format: str) -> None:
|
|
self.logger = logger
|
|
self.log_format = log_format
|
|
|
|
@abstractmethod
|
|
def log(self, request: BaseRequest, response: StreamResponse, time: float) -> None:
|
|
"""Emit log to logger."""
|
|
|
|
|
|
class AbstractAsyncAccessLogger(ABC):
|
|
"""Abstract asynchronous writer to access log."""
|
|
|
|
@abstractmethod
|
|
async def log(
|
|
self, request: BaseRequest, response: StreamResponse, request_start: float
|
|
) -> None:
|
|
"""Emit log to logger."""
|