]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
manager: cli client, extracted constants and tweaks for running locally with supervisord
authorVasek Sraier <git@vakabus.cz>
Mon, 28 Jun 2021 12:59:43 +0000 (14:59 +0200)
committerAleš Mrázek <ales.mrazek@nic.cz>
Fri, 8 Apr 2022 14:17:52 +0000 (16:17 +0200)
78 files changed:
manager/config/knot-resolver-manager.service
manager/etc/knot-resolver/.gitignore [new file with mode: 0644]
manager/etc/knot-resolver/config.yml [new file with mode: 0644]
manager/knot_resolver_manager/__main__.py
manager/knot_resolver_manager/client/__init__.py [new file with mode: 0644]
manager/knot_resolver_manager/client/__main__.py [new file with mode: 0644]
manager/knot_resolver_manager/compat/asyncio.py
manager/knot_resolver_manager/constants.py
manager/knot_resolver_manager/datamodel/network_config.py
manager/knot_resolver_manager/kres_manager.py
manager/knot_resolver_manager/kresd_controller/__init__.py
manager/knot_resolver_manager/kresd_controller/interface.py
manager/knot_resolver_manager/kresd_controller/supervisord/__init__.py
manager/knot_resolver_manager/kresd_controller/supervisord/config.py
manager/knot_resolver_manager/kresd_controller/supervisord/supervisord.conf.j2
manager/knot_resolver_manager/kresd_controller/systemd/__init__.py
manager/knot_resolver_manager/utils/async_utils.py
manager/scripts/run
manager/scripts/run-debug
manager/typings/jinja2/__init__.pyi
manager/typings/jinja2/_compat.pyi
manager/typings/jinja2/_stringdefs.pyi
manager/typings/jinja2/bccache.pyi
manager/typings/jinja2/compiler.pyi
manager/typings/jinja2/constants.pyi
manager/typings/jinja2/debug.pyi
manager/typings/jinja2/defaults.pyi
manager/typings/jinja2/environment.pyi
manager/typings/jinja2/exceptions.pyi
manager/typings/jinja2/ext.pyi
manager/typings/jinja2/filters.pyi
manager/typings/jinja2/lexer.pyi
manager/typings/jinja2/loaders.pyi
manager/typings/jinja2/meta.pyi
manager/typings/jinja2/nodes.pyi
manager/typings/jinja2/optimizer.pyi
manager/typings/jinja2/parser.pyi
manager/typings/jinja2/runtime.pyi
manager/typings/jinja2/sandbox.pyi
manager/typings/jinja2/tests.pyi
manager/typings/jinja2/utils.pyi
manager/typings/jinja2/visitor.pyi
manager/typings/supervisor/__init__.pyi [new file with mode: 0644]
manager/typings/supervisor/childutils.pyi [new file with mode: 0644]
manager/typings/supervisor/compat.pyi [new file with mode: 0644]
manager/typings/supervisor/confecho.pyi [new file with mode: 0644]
manager/typings/supervisor/datatypes.pyi [new file with mode: 0644]
manager/typings/supervisor/dispatchers.pyi [new file with mode: 0644]
manager/typings/supervisor/events.pyi [new file with mode: 0644]
manager/typings/supervisor/http.pyi [new file with mode: 0644]
manager/typings/supervisor/http_client.pyi [new file with mode: 0644]
manager/typings/supervisor/loggers.pyi [new file with mode: 0644]
manager/typings/supervisor/medusa/__init__.pyi [new file with mode: 0644]
manager/typings/supervisor/medusa/asynchat_25.pyi [new file with mode: 0644]
manager/typings/supervisor/medusa/asyncore_25.pyi [new file with mode: 0644]
manager/typings/supervisor/medusa/auth_handler.pyi [new file with mode: 0644]
manager/typings/supervisor/medusa/counter.pyi [new file with mode: 0644]
manager/typings/supervisor/medusa/default_handler.pyi [new file with mode: 0644]
manager/typings/supervisor/medusa/filesys.pyi [new file with mode: 0644]
manager/typings/supervisor/medusa/http_date.pyi [new file with mode: 0644]
manager/typings/supervisor/medusa/http_server.pyi [new file with mode: 0644]
manager/typings/supervisor/medusa/logger.pyi [new file with mode: 0644]
manager/typings/supervisor/medusa/producers.pyi [new file with mode: 0644]
manager/typings/supervisor/medusa/util.pyi [new file with mode: 0644]
manager/typings/supervisor/medusa/xmlrpc_handler.pyi [new file with mode: 0644]
manager/typings/supervisor/options.pyi [new file with mode: 0644]
manager/typings/supervisor/pidproxy.pyi [new file with mode: 0644]
manager/typings/supervisor/poller.pyi [new file with mode: 0644]
manager/typings/supervisor/process.pyi [new file with mode: 0644]
manager/typings/supervisor/rpcinterface.pyi [new file with mode: 0644]
manager/typings/supervisor/socket_manager.pyi [new file with mode: 0644]
manager/typings/supervisor/states.pyi [new file with mode: 0644]
manager/typings/supervisor/supervisorctl.pyi [new file with mode: 0644]
manager/typings/supervisor/supervisord.pyi [new file with mode: 0644]
manager/typings/supervisor/templating.pyi [new file with mode: 0644]
manager/typings/supervisor/tests/__init__.pyi [new file with mode: 0644]
manager/typings/supervisor/web.pyi [new file with mode: 0644]
manager/typings/supervisor/xmlrpc.pyi [new file with mode: 0644]

index c673d1462eab6c5676914280f173b015f02685c8..9261c36ae2be902df894963e8517aeaeae45f2ed 100644 (file)
@@ -5,7 +5,7 @@ After=dbus
 
 [Service]
 WorkingDirectory=/code
-ExecStart=/usr/bin/python3 -m knot_resolver_manager --config=/etc/knot-resolver/kres-manager.yaml
+ExecStart=/usr/bin/python3 -m knot_resolver_manager --config=/etc/knot-resolver/kres-manager.yaml /etc/knot-resolver/manager.sock
 KillSignal=SIGINT
 
 [Install]
diff --git a/manager/etc/knot-resolver/.gitignore b/manager/etc/knot-resolver/.gitignore
new file mode 100644 (file)
index 0000000..7a814b8
--- /dev/null
@@ -0,0 +1,2 @@
+runtime/
+cache/
\ No newline at end of file
diff --git a/manager/etc/knot-resolver/config.yml b/manager/etc/knot-resolver/config.yml
new file mode 100644 (file)
index 0000000..7709b1a
--- /dev/null
@@ -0,0 +1,12 @@
+network:
+    interfaces:
+      - listen: 127.0.0.1@5353
+    edns_buffer_size:
+        downstream: 4K
+options:
+    prediction: true
+cache:
+    storage: .
+    size_max: 100M
+logging:
+    level: 5
index 7c5ae31a93dd2fa03fd23a21213201435323fcc9..739a8ed13da8721d3bb1e9c75634579e53d13d9e 100644 (file)
@@ -5,7 +5,7 @@ from typing import List, Optional, Tuple
 import click
 
 from knot_resolver_manager import compat
-from knot_resolver_manager.constants import LISTEN_SOCKET_PATH, MANAGER_CONFIG_FILE
+from knot_resolver_manager.constants import LISTEN_SOCKET_PATH, LOG_LEVEL, MANAGER_CONFIG_FILE
 from knot_resolver_manager.server import start_server
 from knot_resolver_manager.utils import ignore_exceptions_optional
 
@@ -48,5 +48,5 @@ def main(listen: Optional[str], config: Optional[str]):
 
 
 if __name__ == "__main__":
-    logging.basicConfig(level=logging.INFO)
+    logging.basicConfig(level=LOG_LEVEL)
     main()  # pylint: disable=no-value-for-parameter
diff --git a/manager/knot_resolver_manager/client/__init__.py b/manager/knot_resolver_manager/client/__init__.py
new file mode 100644 (file)
index 0000000..3091295
--- /dev/null
@@ -0,0 +1,19 @@
+import urllib.parse
+
+import requests
+
+
+class KnotManagerClient:
+    def __init__(self, url: str):
+        self._url = url
+
+    def _create_url(self, path: str) -> str:
+        return urllib.parse.urljoin(self._url, path)
+
+    def stop(self):
+        response = requests.post(self._create_url("/stop"))
+        print(response.text)
+
+    def set_num_workers(self, n: int):
+        response = requests.post(self._create_url("/config/server/instances"), data=str(n))
+        print(response.text)
diff --git a/manager/knot_resolver_manager/client/__main__.py b/manager/knot_resolver_manager/client/__main__.py
new file mode 100644 (file)
index 0000000..db2b7e1
--- /dev/null
@@ -0,0 +1,39 @@
+import click
+
+from knot_resolver_manager.client import KnotManagerClient
+
+BASE_URL = "base_url"
+
+
+@click.group()
+@click.option(
+    "-u",
+    "--url",
+    "base_url",
+    nargs=1,
+    default="http://localhost:5000/",
+    help="Set base URL on which the manager communicates",
+)
+@click.pass_context
+def main(ctx: click.Context, base_url: str):
+    ctx.ensure_object(dict)
+    ctx.obj[BASE_URL] = base_url
+
+
+@main.command(help="Shutdown the manager and all workers")
+@click.pass_context
+def stop(ctx: click.Context):
+    client = KnotManagerClient(ctx.obj[BASE_URL])
+    client.stop()
+
+
+@main.command(help="Set number of workers")
+@click.argument("instances", type=int, nargs=1)
+@click.pass_context
+def workers(ctx: click.Context, instances: int):
+    client = KnotManagerClient(ctx.obj[BASE_URL])
+    client.set_num_workers(instances)
+
+
+if __name__ == "__main__":
+    main()  # pylint: disable=no-value-for-parameter
index e51c07135ed08347cad089306dd339f2eacdc277..f7f2e8984f9e0e7378cba1795eb20e4a4bb03e4e 100644 (file)
 
 import asyncio
 import functools
+import logging
 import sys
+from asyncio.futures import Future
 from typing import Any, Awaitable, Callable, Coroutine, Optional, TypeVar
 
 from knot_resolver_manager.utils.types import NoneType
 
+logger = logging.getLogger(__name__)
+
 T = TypeVar("T")
 
 
@@ -28,10 +32,17 @@ def to_thread(func: Callable[..., T], *args: Any, **kwargs: Any) -> Awaitable[T]
     else:
         loop = asyncio.get_event_loop()
         pfunc = functools.partial(func, *args, **kwargs)
-        return loop.run_in_executor(None, pfunc)
+
+        def exc_catcher():
+            try:
+                return pfunc()
+            except BaseException:
+                logger.error("Task in thread failed...", exc_info=True)
+
+        return loop.run_in_executor(None, exc_catcher)
 
 
-def create_task(coro: Coroutine[Any, T, NoneType], name: Optional[str] = None) -> Awaitable[T]:
+def create_task(coro: Coroutine[Any, T, NoneType], name: Optional[str] = None) -> "Future[T]":
     # version 3.8 and higher, call directly
     if sys.version_info.major >= 3 and sys.version_info.minor >= 8:
         return asyncio.create_task(coro, name=name)
index bdefaaa391fef5648a8dbc552352e9ee184fc814..ec68541037fb955a36348610b5f1f94ad45c8d0b 100644 (file)
@@ -1,8 +1,33 @@
+import logging
 from pathlib import Path
 
-CONFIGURATION_DIR = Path("/etc/knot-resolver")
+LOG_LEVEL = logging.DEBUG
+
+CONFIGURATION_DIR = Path("etc/knot-resolver").absolute()
+CONFIGURATION_DIR.mkdir(exist_ok=True)
+RUNTIME_DIR = Path("etc/knot-resolver/runtime").absolute()
+RUNTIME_DIR.mkdir(exist_ok=True)
+KRES_CACHE_DIR = Path("etc/knot-resolver/cache").absolute()
+KRES_CACHE_DIR.mkdir(exist_ok=True)
+
+
+KRESD_EXECUTABLE = Path("/usr/sbin/kresd")
+GC_EXECUTABLE = Path("/usr/sbin/kres-cache-gc")
+# KRES_CACHE_DIR = Path("/var/lib/knot-resolver")
+
+KRESD_CONFIG_FILE = RUNTIME_DIR / "kresd.conf"
+KRESD_SUPERVISORD_ARGS = f"-c {str(KRESD_CONFIG_FILE.absolute())} -n -vvv"
+KRES_GC_SUPERVISORD_ARGS = f"-c {KRES_CACHE_DIR.absolute()} -d 1000"
+
+SUPERVISORD_CONFIG_FILE = RUNTIME_DIR / "supervisord.conf"
+SUPERVISORD_CONFIG_FILE_TMP = RUNTIME_DIR / "supervisord.conf.tmp"
+SUPERVISORD_PID_FILE = RUNTIME_DIR / "supervisord.pid"
+SUPERVISORD_SOCK = RUNTIME_DIR / "supervisord.sock"
+SUPERVISORD_LOGFILE = RUNTIME_DIR / "supervisord.log"
+
+SUPERVISORD_SUBPROCESS_LOG_DIR = RUNTIME_DIR / "logs"
+SUPERVISORD_SUBPROCESS_LOG_DIR.mkdir(exist_ok=True)
 
-KRESD_CONFIG_FILE = CONFIGURATION_DIR / "kresd.conf"
 MANAGER_CONFIG_FILE = CONFIGURATION_DIR / "config.yml"
 
-LISTEN_SOCKET_PATH = CONFIGURATION_DIR / "manager.sock"
+LISTEN_SOCKET_PATH = RUNTIME_DIR / "manager.sock"
index 0acf661395b490e35a1749e99fc8cdcab8366578..5e0f5f70318c18a4a6a55c82086159ca302cb30a 100644 (file)
@@ -17,13 +17,14 @@ class InterfacesConfig(DataclassParserValidatorMixin):
     def __post_init__(self):
         # split 'address@port'
         if "@" in self.listen:
-            tmp = self.listen.split("@", maxsplit=1)
-            self._address = tmp[0]
-            self._port = int(tmp[1])
-        # if port number not specified
-        self._address = self.listen
-        # set port number based on 'kind'
-        self._port = self._kind_port_map.get(self.kind)
+            address, port = self.listen.split("@", maxsplit=1)
+            self._address = address
+            self._port = int(port)
+        else:
+            # if port number not specified
+            self._address = self.listen
+            # set port number based on 'kind'
+            self._port = self._kind_port_map.get(self.kind)
 
     def get_address(self) -> Optional[str]:
         return self._address
index 017e612c858a47b3cbbef99af248eb7a981ed5d7..a07fc0b0594c4e1cad9479f0312a0da90751b1fb 100644 (file)
@@ -1,5 +1,5 @@
 import asyncio
-from typing import Any, List, Optional, Type
+from typing import List, Optional, Type
 from uuid import uuid4
 
 from knot_resolver_manager.constants import KRESD_CONFIG_FILE
@@ -19,9 +19,9 @@ class KresManager:
     """
 
     @classmethod
-    async def create(cls: Type["KresManager"], *args: Any, **kwargs: Any) -> "KresManager":
+    async def create(cls: Type["KresManager"]) -> "KresManager":
         obj = cls()
-        await obj._async_init(*args, **kwargs)  # pylint: disable=protected-access
+        await obj._async_init()  # pylint: disable=protected-access
         return obj
 
     async def _async_init(self):
@@ -80,7 +80,9 @@ class KresManager:
             await self._rolling_restart()
 
     async def stop(self):
-        await self._ensure_number_of_children(0)
+        async with self._manager_lock:
+            await self._ensure_number_of_children(0)
+            await self._controller.shutdown_controller()
 
     def get_last_used_config(self) -> Optional[KresConfig]:
         return self._last_used_config
index 065849d61305a9e2a8759607f6df68db769b835d..a11816117fd0ce7be81e9f614c9264c076ede2ca 100644 (file)
@@ -1,23 +1,49 @@
 import asyncio
 import logging
-from typing import Tuple
+from typing import List
 
 from knot_resolver_manager.kresd_controller.interface import SubprocessController
-from knot_resolver_manager.kresd_controller.supervisord import SupervisordSubprocessController
-from knot_resolver_manager.kresd_controller.systemd import SystemdSubprocessController
-from knot_resolver_manager.kresd_controller.systemd.dbus_api import SystemdType
-
-# In this tuple, every supported controller should be listed. In the order of preference (preferred first)
-_registered_controllers: Tuple[SubprocessController, ...] = (
-    SystemdSubprocessController(SystemdType.SESSION),
-    SystemdSubprocessController(SystemdType.SYSTEM),
-    SupervisordSubprocessController(),
-)
 
 logger = logging.getLogger(__name__)
 
+"""
+List of all subprocess controllers that are available in order of priority.
+It is filled dynamically based on available modules that do not fail to import.
+"""
+_registered_controllers: List[SubprocessController] = []
+
+
+# supervisord
+try:
+    from knot_resolver_manager.kresd_controller.supervisord import SupervisordSubprocessController
+
+    _registered_controllers.append(SupervisordSubprocessController())
+except ImportError:
+    logger.info("Failed to import modules related to supervisord service manager")
+
+
+# systemd
+try:
+    from knot_resolver_manager.kresd_controller.systemd import SystemdSubprocessController
+    from knot_resolver_manager.kresd_controller.systemd.dbus_api import SystemdType
+
+    _registered_controllers.extend(
+        [
+            SystemdSubprocessController(SystemdType.SYSTEM),
+            SystemdSubprocessController(SystemdType.SESSION),
+        ]
+    )
+except ImportError:
+    logger.info("Failed to import modules related to systemd service manager")
+
 
 async def get_best_controller_implementation() -> SubprocessController:
+    logger.debug("Starting service manager auto-selection...")
+
+    if len(_registered_controllers) == 0:
+        logger.error("No controllers are available! Did you install all dependencies?")
+        raise LookupError("No service managers available!")
+
     # check all controllers concurrently
     res = await asyncio.gather(*(cont.is_controller_available() for cont in _registered_controllers))
 
index 7503c3044a1cd5ead2c9bcc001b71ba2e49fcbc1..cadd0c44a203d09becd8c75b6e7a1891293283fb 100644 (file)
@@ -57,6 +57,14 @@ class SubprocessController:
         """
         raise NotImplementedError()
 
+    async def shutdown_controller(self) -> None:
+        """
+        Called when the manager is gracefully shutting down. Allows us to stop
+        the service manager process or simply cleanup, so that we don't reuse
+        the same resources in a new run.
+        """
+        raise NotImplementedError()
+
     async def create_subprocess(self, subprocess_type: SubprocessType, id_hint: str) -> Subprocess:
         """
         Return a Subprocess object which can be operated on. The subprocess is not
index cccc72890b794749f9d7a20da32dbef6a508cc17..576b915349cd7c033dac980afc07766b69a38a2b 100644 (file)
@@ -1,6 +1,8 @@
 import logging
-from typing import Iterable, Set
+from asyncio.futures import Future
+from typing import Any, Iterable, Set
 
+from knot_resolver_manager.compat.asyncio import create_task
 from knot_resolver_manager.kresd_controller.interface import Subprocess, SubprocessController, SubprocessType
 
 from .config import (
@@ -11,7 +13,9 @@ from .config import (
     list_ids_from_existing_config,
     restart,
     start_supervisord,
+    stop_supervisord,
     update_config,
+    watchdog,
 )
 
 logger = logging.getLogger(__name__)
@@ -47,6 +51,7 @@ class SupervisordSubprocess(Subprocess):
 class SupervisordSubprocessController(SubprocessController):
     def __init__(self):
         self._running_instances: Set[SupervisordSubprocess] = set()
+        self._watchdog_task: "Future[Any]"
 
     def __str__(self):
         return type(self).__name__
@@ -78,6 +83,11 @@ class SupervisordSubprocessController(SubprocessController):
         if not await is_supervisord_running():
             config = self._create_config()
             await start_supervisord(config)
+        self._watchdog_task = create_task(watchdog())
+
+    async def shutdown_controller(self) -> None:
+        self._watchdog_task.cancel()
+        await stop_supervisord()
 
     async def start_subprocess(self, subprocess: SupervisordSubprocess):
         assert subprocess not in self._running_instances
index cb2d8d09a715e244a8271c01799be7fe439b9822..0572cde8283d3c827b4c30ff5d1a406d570b8c40 100644 (file)
@@ -1,53 +1,92 @@
+import asyncio
 import configparser
-import os.path
+import logging
+import os
 import signal
+import sys
 from os import kill
 from pathlib import Path
-from typing import List, Set, Tuple
+from typing import Any, List, Optional, Set, Tuple
+from xmlrpc.client import ServerProxy
 
+import supervisor.xmlrpc
 from jinja2 import Template
 
+from knot_resolver_manager.compat.asyncio import to_thread
 from knot_resolver_manager.compat.dataclasses import dataclass
+from knot_resolver_manager.constants import (
+    GC_EXECUTABLE,
+    KRES_CACHE_DIR,
+    KRES_GC_SUPERVISORD_ARGS,
+    KRESD_EXECUTABLE,
+    KRESD_SUPERVISORD_ARGS,
+    SUPERVISORD_CONFIG_FILE,
+    SUPERVISORD_CONFIG_FILE_TMP,
+    SUPERVISORD_LOGFILE,
+    SUPERVISORD_PID_FILE,
+    SUPERVISORD_SOCK,
+    SUPERVISORD_SUBPROCESS_LOG_DIR,
+)
 from knot_resolver_manager.kresd_controller.interface import Subprocess, SubprocessType
-from knot_resolver_manager.utils.async_utils import call, readfile, wait_for_process_termination, writefile
+from knot_resolver_manager.utils.async_utils import (
+    call,
+    read_resource,
+    readfile,
+    wait_for_process_termination,
+    writefile,
+)
 
-CONFIG_FILE = "/tmp/knot-resolver-manager-supervisord.conf"
-PID_FILE = "/tmp/knot-resolver-manager-supervisord.pid"
-SERVER_SOCK = "/tmp/knot-resolver-manager-supervisord.sock"
+WATCHDOG_INTERVAL: int = 15
+
+logger = logging.getLogger(__name__)
 
 
 @dataclass
 class SupervisordConfig:
     instances: Set[Subprocess]
-    unix_http_server: str = SERVER_SOCK
-    pid_file: str = PID_FILE
+    unix_http_server: str = str(SUPERVISORD_SOCK.absolute())
+    pid_file: str = str(SUPERVISORD_PID_FILE.absolute())
 
 
 async def _create_config_file(config: SupervisordConfig):
-    path = Path(os.path.realpath(__file__)).parent / "supervisord.conf.j2"
-    template = await readfile(path)
-    config_string = Template(template).render(config=config)
-    await writefile(CONFIG_FILE, config_string)
+    template = await read_resource(__package__, "supervisord.conf.j2")
+    assert template is not None
+    template = template.decode("utf8")
+    config_string = Template(template).render(
+        config=config,
+        gc_args=KRES_GC_SUPERVISORD_ARGS,
+        kresd_args=KRESD_SUPERVISORD_ARGS,
+        kresd_executable=KRESD_EXECUTABLE,
+        gc_executable=GC_EXECUTABLE,
+        cache_dir=KRES_CACHE_DIR,
+        log_file=SUPERVISORD_LOGFILE,
+        workdir=KRES_CACHE_DIR,
+        log_dir=SUPERVISORD_SUBPROCESS_LOG_DIR,
+    )
+    await writefile(SUPERVISORD_CONFIG_FILE_TMP, config_string)
+    # atomically replace
+    os.rename(SUPERVISORD_CONFIG_FILE_TMP, SUPERVISORD_CONFIG_FILE)
 
 
 async def start_supervisord(config: SupervisordConfig):
     await _create_config_file(config)
-    await call(f'supervisord --configuration="{CONFIG_FILE}"', shell=True)
+    res = await call(f'supervisord --configuration="{SUPERVISORD_CONFIG_FILE.absolute()}"', shell=True)
+    assert res == 0
 
 
 async def stop_supervisord():
-    pid = int(await readfile(PID_FILE))
+    pid = int(await readfile(SUPERVISORD_PID_FILE))
     kill(pid, signal.SIGINT)
     await wait_for_process_termination(pid)
 
 
 async def update_config(config: SupervisordConfig):
     await _create_config_file(config)
-    await call(f'supervisorctl -c "{CONFIG_FILE}" update', shell=True)
+    await call(f'supervisorctl -c "{SUPERVISORD_CONFIG_FILE.absolute()}" update', shell=True)
 
 
 async def restart(id_: str):
-    await call(f'supervisorctl -c "{CONFIG_FILE}" restart {id_}', shell=True)
+    await call(f'supervisorctl -c "{SUPERVISORD_CONFIG_FILE.absolute()}" restart {id_}', shell=True)
 
 
 async def is_supervisord_available() -> bool:
@@ -56,18 +95,42 @@ async def is_supervisord_available() -> bool:
     return i == 0
 
 
-async def is_supervisord_running() -> bool:
-    if not Path(PID_FILE).exists():
-        return False
+async def get_supervisord_pid() -> Optional[int]:
+    if not Path(SUPERVISORD_PID_FILE).exists():
+        return None
+
+    return int(await readfile(SUPERVISORD_PID_FILE))
 
-    pid = int(await readfile(PID_FILE))
+
+def is_process_runinng(pid: int) -> bool:
     try:
+        # kill with signal 0 is a safe way to test that a process exists
         kill(pid, 0)
         return True
     except ProcessLookupError:
         return False
 
 
+async def is_supervisord_running() -> bool:
+    pid = await get_supervisord_pid()
+    if pid is None:
+        return False
+    elif not is_process_runinng(pid):
+        SUPERVISORD_PID_FILE.unlink()
+        return False
+    else:
+        return True
+
+
+def list_fatal_subprocesses_ids() -> List[str]:
+    proxy = ServerProxy(
+        "http://127.0.0.1",
+        transport=supervisor.xmlrpc.SupervisorTransport(None, None, serverurl="unix://" + str(SUPERVISORD_SOCK)),
+    )
+    processes: Any = proxy.supervisor.getAllProcessInfo()
+    return [pr["name"] for pr in processes if pr["statename"] == "FATAL"]
+
+
 def create_id(type_name: SubprocessType, id_: str) -> str:
     return f"{type_name.name}_{id_}"
 
@@ -78,7 +141,7 @@ def parse_id(id_: str) -> Tuple[SubprocessType, str]:
 
 
 async def list_ids_from_existing_config() -> List[Tuple[SubprocessType, str]]:
-    config = await readfile(CONFIG_FILE)
+    config = await readfile(SUPERVISORD_CONFIG_FILE)
     cp = configparser.ConfigParser()
     cp.read_string(config)
 
@@ -88,3 +151,39 @@ async def list_ids_from_existing_config() -> List[Tuple[SubprocessType, str]]:
             program_id = section.replace("program:", "")
             res.append(parse_id(program_id))
     return res
+
+
+async def watchdog() -> None:
+    while True:
+        # the sleep is split into two, because is very likely a problem will occur
+        # just after start
+        await asyncio.sleep(10)
+
+        logger.debug("Watchdog running and checking system's sanity")
+
+        # check that supervisord is running fine
+        if not await is_supervisord_running():
+            logger.error(
+                "Supervisord is not running! It might have crashed, it might "
+                "have been stopped. This behavior is unexpected and we can't "
+                "really tell what's happening right now. The safest option is "
+                "to terminate... Sorry and bye!"
+            )
+            sys.exit(1)
+
+        # check that there are no subprocesses in FATAL state
+        fatals = await to_thread(list_fatal_subprocesses_ids)
+        if len(fatals) != 0:
+            logger.error(
+                "Some kresd instances are in a FATAL state. The configuration "
+                "provided was probably invalid and passed the validation. You "
+                "might find it helpful to look at logfiles for ids %s."
+                "Sadly, there is no safer way forward than a simple suicide.",
+                fatals,
+            )
+            logger.info("Killing supervisord")
+            await stop_supervisord()
+            logger.info("So Long, and Thanks for All the Fish")
+            sys.exit(1)
+
+        await asyncio.sleep(WATCHDOG_INTERVAL - 10)
index 3079e63f1da8fb755523b4931bd35d49446fddc7..ddd0525d1cd2641456b199bdc9f62fd8d3364876 100644 (file)
@@ -1,10 +1,10 @@
 [supervisord]
 pidfile = {{ config.pid_file }}
-directory = /tmp
+directory = {{ workdir }}
 nodaemon = false
-logfile = /tmp/supervisord.log
+logfile = {{ log_file }}
 logfile_maxbytes = 50MB
-user=root
+{# user=root #}
 
 [unix_http_server]
 file = {{ config.unix_http_server }}
@@ -21,16 +21,19 @@ supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
 {% for instance in config.instances %}
 
 [program:{{ instance.id }}]
+redirect_stderr=false
+stdout_logfile={{ log_dir / (instance.id + ".log") }}
+stderr_logfile={{ log_dir / (instance.id + ".log") }}
 
 {%- if instance.type.name == "KRESD" %}
 
-directory=/var/lib/knot-resolver
-command=/usr/sbin/kresd -c /usr/lib/knot-resolver/distro-preconfig.lua -c /etc/knot-resolver/kresd.conf -n
+directory={{ workdir }}
+command={{ kresd_executable }} {{ kresd_args }}
 environment=SYSTEMD_INSTANCE={{ instance.id }}
 {%- elif instance.type.name == "GC" %}
 
-directory=/var/lib/knot-resolver
-command=/usr/sbin/kres-cache-gc -c /var/cache/knot-resolver -d 1000
+directory={{ workdir }}
+command={{ gc_executable}} {{ gc_args }}
 
 {%- else %}
 
index 8119775ce124156515715e50615d9020fc7089be..399e8c6006b37de52492d6f661743410f2adbef2 100644 (file)
@@ -1,4 +1,5 @@
 import logging
+import os
 from typing import Iterable, List
 
 from knot_resolver_manager import compat
@@ -67,6 +68,13 @@ class SystemdSubprocessController(SubprocessController):
                 logger.info("Systemd (%s) accessible, but no 'kresd@.service' unit detected.", self._systemd_type)
                 return False
 
+            if self._systemd_type == systemd.SystemdType.SYSTEM and os.geteuid() != 0:
+                logger.info(
+                    "Systemd (%s) looks functional, but we are not running as root. Assuming not enough privileges",
+                    self._systemd_type,
+                )
+                return False
+
             return True
         except BaseException:  # we want every possible exception to be caught
             logger.warning("Communicating with systemd DBus API failed", exc_info=True)
@@ -85,5 +93,8 @@ class SystemdSubprocessController(SubprocessController):
     async def initialize_controller(self) -> None:
         pass
 
+    async def shutdown_controller(self) -> None:
+        pass
+
     async def create_subprocess(self, subprocess_type: SubprocessType, id_hint: str) -> Subprocess:
         return SystemdSubprocess(subprocess_type, id_hint, self._systemd_type)
index 7fbcb18cc16f8b84735f8e2235575a0549a1c133..1b22781e7e14d5b72c5263e9336c2da67df51a0a 100644 (file)
@@ -1,9 +1,10 @@
 import asyncio
 import os
+import pkgutil
 import time
 from asyncio import create_subprocess_exec, create_subprocess_shell
 from pathlib import PurePath
-from typing import List, Union
+from typing import List, Optional, Union
 
 from knot_resolver_manager.compat.asyncio import to_thread
 
@@ -76,3 +77,7 @@ async def wait_for_process_termination(pid: int, sleep_sec: float = 0):
                 break
 
     await to_thread(wait_sync, pid, sleep_sec)
+
+
+async def read_resource(package: str, filename: str) -> Optional[bytes]:
+    return await to_thread(pkgutil.get_data, package, filename)
index 4fabdf692dc7e34d5405ca800395273b92fd44d8..a975fb649d9cfe586790cf27349d9a3719ae4d9d 100755 (executable)
@@ -4,10 +4,7 @@
 src_dir="$(dirname "$(realpath "$0")")"
 source $src_dir/_env.sh
 
-# build dev container
-poe container build dev
-
-echo Knot Manager API is accessible on http://localhost:9000
+echo Knot Manager API is accessible on http://localhost:5000
 echo -------------------------------------------------------
 
-poe container run --code -p 9000 -- knot-manager:dev python -m knot_resolver_manager 9000
\ No newline at end of file
+poetry run python -m knot_resolver_manager 5000 # -c config/kres-manager.yaml
\ No newline at end of file
index 0b61bebb545af8302bf521455c4f77b5efc8f34d..f1754ec01afd020e082d43b0147b9a75e20a0816 100755 (executable)
@@ -4,13 +4,10 @@
 src_dir="$(dirname "$(realpath "$0")")"
 source $src_dir/_env.sh
 
-# build dev container
-poe container build dev
-
 echo The debug server will be listening on port localhost:5678
 echo Use VSCode remote attach feature to connect to the debug server
 echo The manager will start after you connect
 echo API will be running on port 5000
 echo ----------------------------------------
 
-poe container run -p 5000 -p 5678 --code -- knot-manager:dev python -m debugpy --listen 0.0.0.0:5678 --wait-for-client -m knot_resolver_manager 5000
\ No newline at end of file
+poetry run python -m debugpy --listen 0.0.0.0:5678 --wait-for-client -m knot_resolver_manager 5000
\ No newline at end of file
index 1593c9ecbb18e8291294e641d58f0bc701ac3f0b..f7bac8fc24ea4bf54646ada8991ec81f20a3d3cd 100644 (file)
@@ -36,3 +36,7 @@ from jinja2.utils import escape as escape
 from jinja2.utils import evalcontextfunction as evalcontextfunction
 from jinja2.utils import is_undefined as is_undefined
 from jinja2.utils import select_autoescape as select_autoescape
+
+"""
+This type stub file was generated by pyright.
+"""
index 4f1a9596ac1743ac38437707601c4565a3e03b3d..c119f2b1724b1671577dbd1c4e8e9ad694968297 100644 (file)
@@ -4,10 +4,12 @@ This type stub file was generated by pyright.
 
 import sys
 from typing import Any, Optional
-from urllib.parse import quote_from_bytes
 
+"""
+This type stub file was generated by pyright.
+"""
 if sys.version_info >= (3, ):
-    url_quote = quote_from_bytes
+    url_quote = ...
 else:
     ...
 PY2: Any
index 6a3f053aa5bd1c9aa88b5208547a6d8cbbf0ef58..a77d31902ff28a81d08298584dad52d83e96f3a5 100644 (file)
@@ -4,6 +4,9 @@ This type stub file was generated by pyright.
 
 from typing import Any
 
+"""
+This type stub file was generated by pyright.
+"""
 Cc: str
 Cf: str
 Cn: str
index 75b2e6dc11d7dd9f9cb67ac201a08207edb72bab..86dc6b9a3d07433c32098a53497241557747da53 100644 (file)
@@ -4,6 +4,9 @@ This type stub file was generated by pyright.
 
 from typing import Any, Optional
 
+"""
+This type stub file was generated by pyright.
+"""
 marshal_dump: Any
 marshal_load: Any
 bc_version: int
index a8133fea988ff2902e535b029897f0a622f746b4..a528de71552c445d1b7e4d68fec553a9aa7b2403 100644 (file)
@@ -6,6 +6,9 @@ from typing import Any, Optional
 
 from jinja2.visitor import NodeVisitor
 
+"""
+This type stub file was generated by pyright.
+"""
 operators: Any
 dict_item_iter: str
 unoptimize_before_dead_code: bool
index 3ddd49a6d379a9f1b968ee8c7b4b481f9a1710d6..e33f474c4ceb539481cf48a1353754fc97c206f1 100644 (file)
@@ -2,4 +2,7 @@
 This type stub file was generated by pyright.
 """
 
+"""
+This type stub file was generated by pyright.
+"""
 LOREM_IPSUM_WORDS: str
index 8098fcde20d73625a4e85dcd17f4cc34b85064df..c6ca8199aac4d697b89ef12db67e278fa21e6a4f 100644 (file)
@@ -4,6 +4,9 @@ This type stub file was generated by pyright.
 
 from typing import Any, Optional
 
+"""
+This type stub file was generated by pyright.
+"""
 tproxy: Any
 raise_helper: str
 class TracebackFrameProxy:
index 686cef190d9387979c63fd575522bc909bec2279..e1987ed02b0f140b610e4b6332a221be2c3e74a5 100644 (file)
@@ -7,6 +7,9 @@ from typing import Any, Dict, Optional
 from jinja2.filters import FILTERS
 from jinja2.tests import TESTS
 
+"""
+This type stub file was generated by pyright.
+"""
 DEFAULT_FILTERS = FILTERS
 DEFAULT_TESTS = TESTS
 BLOCK_START_STRING: str
index c0fdb5ff2f3e81f8da1cca0d17edd6ba34e68a30..801a1078a03e65206a3b3689319f9fe1d2d11248 100644 (file)
@@ -9,6 +9,9 @@ from .bccache import BytecodeCache
 from .loaders import BaseLoader
 from .runtime import Context, Undefined
 
+"""
+This type stub file was generated by pyright.
+"""
 if sys.version_info >= (3, 6):
     ...
 def get_spontaneous_environment(*args):
index e0cff46447f219acc81a2a92ec5f208374cce735..a9b12102d2a80912d0acfac11dedaa64ab9e1d91 100644 (file)
@@ -4,6 +4,9 @@ This type stub file was generated by pyright.
 
 from typing import Any, Optional, Text
 
+"""
+This type stub file was generated by pyright.
+"""
 class TemplateError(Exception):
     def __init__(self, message: Optional[Text] = ...) -> None:
         ...
index 2fb8826a2a3a619c547b7bdfb8abb186bac00ae4..d86a40e2fb99a42f8f7036f15d028de3170ade2b 100644 (file)
@@ -4,6 +4,9 @@ This type stub file was generated by pyright.
 
 from typing import Any, Optional
 
+"""
+This type stub file was generated by pyright.
+"""
 GETTEXT_FUNCTIONS: Any
 class ExtensionRegistry(type):
     def __new__(cls, name, bases, d):
index 9e758f2c4047717b26caa72fd55489353efc807f..12ba6837d78a0fedc5db671df1393f7bc82bfeda 100644 (file)
@@ -4,6 +4,9 @@ This type stub file was generated by pyright.
 
 from typing import Any, NamedTuple, Optional
 
+"""
+This type stub file was generated by pyright.
+"""
 def contextfilter(f):
     ...
 
index a59d6bdf98dadfb06102c041c4f6c13883001476..08f5f94192b2f118e7033117d5aa91cbbe21fe37 100644 (file)
@@ -4,6 +4,9 @@ This type stub file was generated by pyright.
 
 from typing import Any, Optional, Tuple
 
+"""
+This type stub file was generated by pyright.
+"""
 whitespace_re: Any
 string_re: Any
 integer_re: Any
index fc094aaf876bce3f44129c6609ce6dfa2509057e..a0d660bc7acbd9991d5d23b44b73deb09b31cc51 100644 (file)
@@ -8,6 +8,9 @@ from typing import Any, Callable, Iterable, List, Optional, Text, Tuple, Union
 
 from .environment import Environment
 
+"""
+This type stub file was generated by pyright.
+"""
 if sys.version_info >= (3, 7):
     ...
 else:
index 2de73ca47ab52c43b180bff58bb61a5e1273a050..d5c6d663f4c6ce660d1a16d334c75c4c4ce9db53 100644 (file)
@@ -6,6 +6,9 @@ from typing import Any
 
 from jinja2.compiler import CodeGenerator
 
+"""
+This type stub file was generated by pyright.
+"""
 class TrackingCodeGenerator(CodeGenerator):
     undeclared_identifiers: Any
     def __init__(self, environment) -> None:
index c7213b5d1c70f7c65da7bba6b1898cba4e72c658..54859f61dd5dfc2fc338112f75686953f6a5e4c0 100644 (file)
@@ -5,6 +5,9 @@ This type stub file was generated by pyright.
 import typing
 from typing import Any, Optional
 
+"""
+This type stub file was generated by pyright.
+"""
 class Impossible(Exception):
     ...
 
index ff77b4178857728586612fbe7626020bb03dac2e..f118f9a28c454bf3e14d111539ee6dc99760eaad 100644 (file)
@@ -6,6 +6,9 @@ from typing import Any
 
 from jinja2.visitor import NodeTransformer
 
+"""
+This type stub file was generated by pyright.
+"""
 def optimize(node, environment):
     ...
 
index 95b717b903c5e91ba669fc36d6dd92701f68eb80..5ce300444eabea734d1f255c33bd183c1da7d201 100644 (file)
@@ -4,6 +4,9 @@ This type stub file was generated by pyright.
 
 from typing import Any, Optional
 
+"""
+This type stub file was generated by pyright.
+"""
 class Parser:
     environment: Any
     stream: Any
index 0c0aa0c83e0a82a6f4d1d57ac6261bcc485b448e..0ed45eeca8285b81d3677f638ef91f1f3a7b827b 100644 (file)
@@ -6,6 +6,9 @@ from typing import Any, Dict, Optional, Text, Union
 
 from jinja2.environment import Environment
 
+"""
+This type stub file was generated by pyright.
+"""
 to_string: Any
 identity: Any
 def markup_join(seq):
index b5bca117aa092b1bbab97fc936a3e60a5f1b67ff..1940479c96ba4fab25df13c0774b3f7e6db59976 100644 (file)
@@ -6,6 +6,9 @@ from typing import Any
 
 from jinja2.environment import Environment
 
+"""
+This type stub file was generated by pyright.
+"""
 MAX_RANGE: int
 UNSAFE_FUNCTION_ATTRIBUTES: Any
 UNSAFE_METHOD_ATTRIBUTES: Any
index 7d2f7596cc491b4454082a27c26d17f49f1dd7a7..3a7139a2faa299ee8075cf48e8b3b0ac47b5905f 100644 (file)
@@ -4,6 +4,9 @@ This type stub file was generated by pyright.
 
 from typing import Any
 
+"""
+This type stub file was generated by pyright.
+"""
 number_re: Any
 regex_type: Any
 test_callable: Any
index 42c88ae2010c02b3d248c96c2a45c764af554fc1..77f2bec8fcd3c568f259d2b69a31fe67472d9e34 100644 (file)
@@ -2,17 +2,20 @@
 This type stub file was generated by pyright.
 """
 
-from typing import IO, Any, Callable, Iterable, Optional, Protocol, Text, TypeVar, Union
+from typing import IO, Any, Callable, Iterable, Optional, Protocol, Text, Union
 
 from markupsafe import Markup as Markup
 from typing_extensions import Literal
 
 from _typeshed import AnyPath
 
+"""
+This type stub file was generated by pyright.
+"""
 missing: Any
 internal_code: Any
 concat: Any
-_CallableT = TypeVar("_CallableT", bound=Callable[..., Any])
+_CallableT = ...
 class _ContextFunction(Protocol[_CallableT]):
     contextfunction: Literal[True]
     __call__: _CallableT
index e05e056aec0855b48cd0997a26102a039cf0e0fa..963062797c5d702aa7855837ba0b1e2e6e5da81c 100644 (file)
@@ -2,6 +2,9 @@
 This type stub file was generated by pyright.
 """
 
+"""
+This type stub file was generated by pyright.
+"""
 class NodeVisitor:
     def get_visitor(self, node):
         ...
diff --git a/manager/typings/supervisor/__init__.pyi b/manager/typings/supervisor/__init__.pyi
new file mode 100644 (file)
index 0000000..cea7ef9
--- /dev/null
@@ -0,0 +1,3 @@
+"""
+This type stub file was generated by pyright.
+"""
diff --git a/manager/typings/supervisor/childutils.pyi b/manager/typings/supervisor/childutils.pyi
new file mode 100644 (file)
index 0000000..7845a5a
--- /dev/null
@@ -0,0 +1,51 @@
+"""
+This type stub file was generated by pyright.
+"""
+
+def getRPCTransport(env): # -> SupervisorTransport:
+    ...
+
+def getRPCInterface(env):
+    ...
+
+def get_headers(line): # -> dict[Unknown, Unknown]:
+    ...
+
+def eventdata(payload): # -> tuple[dict[Unknown, Unknown], Unknown]:
+    ...
+
+def get_asctime(now=...): # -> str:
+    ...
+
+class ProcessCommunicationsProtocol:
+    def send(self, msg, fp=...): # -> None:
+        ...
+    
+    def stdout(self, msg): # -> None:
+        ...
+    
+    def stderr(self, msg): # -> None:
+        ...
+    
+
+
+pcomm = ...
+class EventListenerProtocol:
+    def wait(self, stdin=..., stdout=...): # -> tuple[dict[Unknown, Unknown], Unknown]:
+        ...
+    
+    def ready(self, stdout=...): # -> None:
+        ...
+    
+    def ok(self, stdout=...): # -> None:
+        ...
+    
+    def fail(self, stdout=...): # -> None:
+        ...
+    
+    def send(self, data, stdout=...): # -> None:
+        ...
+    
+
+
+listener = ...
diff --git a/manager/typings/supervisor/compat.pyi b/manager/typings/supervisor/compat.pyi
new file mode 100644 (file)
index 0000000..72fc9a4
--- /dev/null
@@ -0,0 +1,39 @@
+"""
+This type stub file was generated by pyright.
+"""
+
+PY2 = ...
+if PY2:
+    long = ...
+    raw_input = ...
+    unicode = ...
+    unichr = ...
+    basestring = ...
+    def as_bytes(s, encoding=...): # -> str:
+        ...
+    
+    def as_string(s, encoding=...): # -> unicode:
+        ...
+    
+    def is_text_stream(stream): # -> bool:
+        ...
+    
+else:
+    long = ...
+    basestring = ...
+    raw_input = ...
+    unichr = ...
+    class unicode(str):
+        def __init__(self, string, encoding, errors) -> None:
+            ...
+        
+    
+    
+    def as_bytes(s, encoding=...): # -> bytes:
+        ...
+    
+    def as_string(s, encoding=...): # -> str:
+        ...
+    
+    def is_text_stream(stream): # -> bool:
+        ...
diff --git a/manager/typings/supervisor/confecho.pyi b/manager/typings/supervisor/confecho.pyi
new file mode 100644 (file)
index 0000000..9cd6e68
--- /dev/null
@@ -0,0 +1,6 @@
+"""
+This type stub file was generated by pyright.
+"""
+
+def main(out=...): # -> None:
+    ...
diff --git a/manager/typings/supervisor/datatypes.pyi b/manager/typings/supervisor/datatypes.pyi
new file mode 100644 (file)
index 0000000..5ef53bc
--- /dev/null
@@ -0,0 +1,199 @@
+"""
+This type stub file was generated by pyright.
+"""
+
+def process_or_group_name(name): # -> str:
+    """Ensures that a process or group name is not created with
+       characters that break the eventlistener protocol or web UI URLs"""
+    ...
+
+def integer(value): # -> int:
+    ...
+
+TRUTHY_STRINGS = ...
+FALSY_STRINGS = ...
+def boolean(s): # -> bool:
+    """Convert a string value to a boolean value."""
+    ...
+
+def list_of_strings(arg): # -> list[Unknown]:
+    ...
+
+def list_of_ints(arg): # -> list[int]:
+    ...
+
+def list_of_exitcodes(arg): # -> list[int]:
+    ...
+
+def dict_of_key_value_pairs(arg): # -> dict[Unknown, Unknown]:
+    """ parse KEY=val,KEY2=val2 into {'KEY':'val', 'KEY2':'val2'}
+        Quotes can be used to allow commas in the value
+    """
+    ...
+
+class Automatic:
+    ...
+
+
+class Syslog:
+    """TODO deprecated; remove this special 'syslog' filename in the future"""
+    ...
+
+
+LOGFILE_NONES = ...
+LOGFILE_AUTOS = ...
+LOGFILE_SYSLOGS = ...
+def logfile_name(val): # -> Type[Automatic] | Type[Syslog] | None:
+    ...
+
+class RangeCheckedConversion:
+    """Conversion helper that range checks another conversion."""
+    def __init__(self, conversion, min=..., max=...) -> None:
+        ...
+    
+    def __call__(self, value):
+        ...
+    
+
+
+port_number = ...
+def inet_address(s): # -> tuple[Unknown | Literal[''], Unknown]:
+    ...
+
+class SocketAddress:
+    def __init__(self, s) -> None:
+        ...
+    
+
+
+class SocketConfig:
+    """ Abstract base class which provides a uniform abstraction
+    for TCP vs Unix sockets """
+    url = ...
+    addr = ...
+    backlog = ...
+    def __repr__(self): # -> str:
+        ...
+    
+    def __str__(self) -> str:
+        ...
+    
+    def __eq__(self, other) -> bool:
+        ...
+    
+    def __ne__(self, other) -> bool:
+        ...
+    
+    def get_backlog(self): # -> None:
+        ...
+    
+    def addr(self):
+        ...
+    
+    def create_and_bind(self):
+        ...
+    
+
+
+class InetStreamSocketConfig(SocketConfig):
+    """ TCP socket config helper """
+    host = ...
+    port = ...
+    def __init__(self, host, port, **kwargs) -> None:
+        ...
+    
+    def addr(self): # -> tuple[Unknown | None, Unknown | None]:
+        ...
+    
+    def create_and_bind(self): # -> socket:
+        ...
+    
+
+
+class UnixStreamSocketConfig(SocketConfig):
+    """ Unix domain socket config helper """
+    path = ...
+    mode = ...
+    owner = ...
+    sock = ...
+    def __init__(self, path, **kwargs) -> None:
+        ...
+    
+    def addr(self): # -> Unknown | None:
+        ...
+    
+    def create_and_bind(self): # -> socket:
+        ...
+    
+    def get_mode(self): # -> None:
+        ...
+    
+    def get_owner(self): # -> None:
+        ...
+    
+
+
+def colon_separated_user_group(arg): # -> tuple[int, int]:
+    """ Find a user ID and group ID from a string like 'user:group'.  Returns
+        a tuple (uid, gid).  If the string only contains a user like 'user'
+        then (uid, -1) will be returned.  Raises ValueError if either
+        the user or group can't be resolved to valid IDs on the system. """
+    ...
+
+def name_to_uid(name): # -> int:
+    """ Find a user ID from a string containing a user name or ID.
+        Raises ValueError if the string can't be resolved to a valid
+        user ID on the system. """
+    ...
+
+def name_to_gid(name): # -> int:
+    """ Find a group ID from a string containing a group name or ID.
+        Raises ValueError if the string can't be resolved to a valid
+        group ID on the system. """
+    ...
+
+def gid_for_uid(uid): # -> int:
+    ...
+
+def octal_type(arg): # -> int:
+    ...
+
+def existing_directory(v):
+    ...
+
+def existing_dirpath(v):
+    ...
+
+def logging_level(value): # -> Any:
+    ...
+
+class SuffixMultiplier:
+    def __init__(self, d, default=...) -> None:
+        ...
+    
+    def __call__(self, v): # -> int:
+        ...
+    
+
+
+byte_size = ...
+def url(value):
+    ...
+
+SIGNUMS = ...
+def signal_number(value): # -> int | Any:
+    ...
+
+class RestartWhenExitUnexpected:
+    ...
+
+
+class RestartUnconditionally:
+    ...
+
+
+def auto_restart(value): # -> Type[RestartUnconditionally] | Type[RestartWhenExitUnexpected] | str | Literal[False]:
+    ...
+
+def profile_options(value): # -> tuple[list[Unknown], bool]:
+    ...
diff --git a/manager/typings/supervisor/dispatchers.pyi b/manager/typings/supervisor/dispatchers.pyi
new file mode 100644 (file)
index 0000000..dfeff5c
--- /dev/null
@@ -0,0 +1,158 @@
+"""
+This type stub file was generated by pyright.
+"""
+
+def find_prefix_at_end(haystack, needle): # -> int:
+    ...
+
+class PDispatcher:
+    """ Asyncore dispatcher for mainloop, representing a process channel
+    (stdin, stdout, or stderr).  This class is abstract. """
+    closed = ...
+    def __init__(self, process, channel, fd) -> None:
+        ...
+    
+    def __repr__(self): # -> str:
+        ...
+    
+    def readable(self):
+        ...
+    
+    def writable(self):
+        ...
+    
+    def handle_read_event(self):
+        ...
+    
+    def handle_write_event(self):
+        ...
+    
+    def handle_error(self): # -> None:
+        ...
+    
+    def close(self): # -> None:
+        ...
+    
+    def flush(self): # -> None:
+        ...
+    
+
+
+class POutputDispatcher(PDispatcher):
+    """
+    Dispatcher for one channel (stdout or stderr) of one process.
+    Serves several purposes:
+
+    - capture output sent within <!--XSUPERVISOR:BEGIN--> and
+      <!--XSUPERVISOR:END--> tags and signal a ProcessCommunicationEvent
+      by calling notify(event).
+    - route the output to the appropriate log handlers as specified in the
+      config.
+    """
+    childlog = ...
+    normallog = ...
+    capturelog = ...
+    capturemode = ...
+    output_buffer = ...
+    def __init__(self, process, event_type, fd) -> None:
+        """
+        Initialize the dispatcher.
+
+        `event_type` should be one of ProcessLogStdoutEvent or
+        ProcessLogStderrEvent
+        """
+        ...
+    
+    def removelogs(self): # -> None:
+        ...
+    
+    def reopenlogs(self): # -> None:
+        ...
+    
+    def record_output(self): # -> None:
+        ...
+    
+    def toggle_capturemode(self): # -> None:
+        ...
+    
+    def writable(self): # -> Literal[False]:
+        ...
+    
+    def readable(self): # -> bool:
+        ...
+    
+    def handle_read_event(self): # -> None:
+        ...
+    
+
+
+class PEventListenerDispatcher(PDispatcher):
+    """ An output dispatcher that monitors and changes a process'
+    listener_state """
+    childlog = ...
+    state_buffer = ...
+    READY_FOR_EVENTS_TOKEN = ...
+    RESULT_TOKEN_START = ...
+    READY_FOR_EVENTS_LEN = ...
+    RESULT_TOKEN_START_LEN = ...
+    def __init__(self, process, channel, fd) -> None:
+        ...
+    
+    def removelogs(self): # -> None:
+        ...
+    
+    def reopenlogs(self): # -> None:
+        ...
+    
+    def writable(self): # -> Literal[False]:
+        ...
+    
+    def readable(self): # -> bool:
+        ...
+    
+    def handle_read_event(self): # -> None:
+        ...
+    
+    def handle_listener_state_change(self): # -> None:
+        ...
+    
+    def handle_result(self, result): # -> None:
+        ...
+    
+
+
+class PInputDispatcher(PDispatcher):
+    """ Input (stdin) dispatcher """
+    def __init__(self, process, channel, fd) -> None:
+        ...
+    
+    def writable(self): # -> bool:
+        ...
+    
+    def readable(self): # -> Literal[False]:
+        ...
+    
+    def flush(self): # -> None:
+        ...
+    
+    def handle_write_event(self): # -> None:
+        ...
+    
+
+
+ANSI_ESCAPE_BEGIN = ...
+ANSI_TERMINATORS = ...
+def stripEscapes(s): # -> Literal[b'']:
+    """
+    Remove all ANSI color escapes from the given string.
+    """
+    ...
+
+class RejectEvent(Exception):
+    """ The exception type expected by a dispatcher when a handler wants
+    to reject an event """
+    ...
+
+
+def default_handler(event, response): # -> None:
+    ...
diff --git a/manager/typings/supervisor/events.pyi b/manager/typings/supervisor/events.pyi
new file mode 100644 (file)
index 0000000..85c6662
--- /dev/null
@@ -0,0 +1,227 @@
+"""
+This type stub file was generated by pyright.
+"""
+
+callbacks = ...
+def subscribe(type, callback): # -> None:
+    ...
+
+def unsubscribe(type, callback): # -> None:
+    ...
+
+def notify(event): # -> None:
+    ...
+
+def clear(): # -> None:
+    ...
+
+class Event:
+    """ Abstract event type """
+    ...
+
+
+class ProcessLogEvent(Event):
+    """ Abstract """
+    channel = ...
+    def __init__(self, process, pid, data) -> None:
+        ...
+    
+    def payload(self): # -> str:
+        ...
+    
+
+
+class ProcessLogStdoutEvent(ProcessLogEvent):
+    channel = ...
+
+
+class ProcessLogStderrEvent(ProcessLogEvent):
+    channel = ...
+
+
+class ProcessCommunicationEvent(Event):
+    """ Abstract """
+    BEGIN_TOKEN = ...
+    END_TOKEN = ...
+    def __init__(self, process, pid, data) -> None:
+        ...
+    
+    def payload(self): # -> str:
+        ...
+    
+
+
+class ProcessCommunicationStdoutEvent(ProcessCommunicationEvent):
+    channel = ...
+
+
+class ProcessCommunicationStderrEvent(ProcessCommunicationEvent):
+    channel = ...
+
+
+class RemoteCommunicationEvent(Event):
+    def __init__(self, type, data) -> None:
+        ...
+    
+    def payload(self): # -> str:
+        ...
+    
+
+
+class SupervisorStateChangeEvent(Event):
+    """ Abstract class """
+    def payload(self): # -> Literal['']:
+        ...
+    
+
+
+class SupervisorRunningEvent(SupervisorStateChangeEvent):
+    ...
+
+
+class SupervisorStoppingEvent(SupervisorStateChangeEvent):
+    ...
+
+
+class EventRejectedEvent:
+    def __init__(self, process, event) -> None:
+        ...
+    
+
+
+class ProcessStateEvent(Event):
+    """ Abstract class, never raised directly """
+    frm = ...
+    to = ...
+    def __init__(self, process, from_state, expected=...) -> None:
+        ...
+    
+    def payload(self): # -> str:
+        ...
+    
+    def get_extra_values(self): # -> list[Unknown]:
+        ...
+    
+
+
+class ProcessStateFatalEvent(ProcessStateEvent):
+    ...
+
+
+class ProcessStateUnknownEvent(ProcessStateEvent):
+    ...
+
+
+class ProcessStateStartingOrBackoffEvent(ProcessStateEvent):
+    def get_extra_values(self): # -> list[tuple[Literal['tries'], int]]:
+        ...
+    
+
+
+class ProcessStateBackoffEvent(ProcessStateStartingOrBackoffEvent):
+    ...
+
+
+class ProcessStateStartingEvent(ProcessStateStartingOrBackoffEvent):
+    ...
+
+
+class ProcessStateExitedEvent(ProcessStateEvent):
+    def get_extra_values(self): # -> list[tuple[Literal['expected'], int] | tuple[Literal['pid'], Unknown]]:
+        ...
+    
+
+
+class ProcessStateRunningEvent(ProcessStateEvent):
+    def get_extra_values(self): # -> list[tuple[Literal['pid'], Unknown]]:
+        ...
+    
+
+
+class ProcessStateStoppingEvent(ProcessStateEvent):
+    def get_extra_values(self): # -> list[tuple[Literal['pid'], Unknown]]:
+        ...
+    
+
+
+class ProcessStateStoppedEvent(ProcessStateEvent):
+    def get_extra_values(self): # -> list[tuple[Literal['pid'], Unknown]]:
+        ...
+    
+
+
+class ProcessGroupEvent(Event):
+    def __init__(self, group) -> None:
+        ...
+    
+    def payload(self): # -> str:
+        ...
+    
+
+
+class ProcessGroupAddedEvent(ProcessGroupEvent):
+    ...
+
+
+class ProcessGroupRemovedEvent(ProcessGroupEvent):
+    ...
+
+
+class TickEvent(Event):
+    """ Abstract """
+    def __init__(self, when, supervisord) -> None:
+        ...
+    
+    def payload(self): # -> str:
+        ...
+    
+
+
+class Tick5Event(TickEvent):
+    period = ...
+
+
+class Tick60Event(TickEvent):
+    period = ...
+
+
+class Tick3600Event(TickEvent):
+    period = ...
+
+
+TICK_EVENTS = ...
+class EventTypes:
+    EVENT = Event
+    PROCESS_STATE = ProcessStateEvent
+    PROCESS_STATE_STOPPED = ProcessStateStoppedEvent
+    PROCESS_STATE_EXITED = ProcessStateExitedEvent
+    PROCESS_STATE_STARTING = ProcessStateStartingEvent
+    PROCESS_STATE_STOPPING = ProcessStateStoppingEvent
+    PROCESS_STATE_BACKOFF = ProcessStateBackoffEvent
+    PROCESS_STATE_FATAL = ProcessStateFatalEvent
+    PROCESS_STATE_RUNNING = ProcessStateRunningEvent
+    PROCESS_STATE_UNKNOWN = ProcessStateUnknownEvent
+    PROCESS_COMMUNICATION = ProcessCommunicationEvent
+    PROCESS_COMMUNICATION_STDOUT = ProcessCommunicationStdoutEvent
+    PROCESS_COMMUNICATION_STDERR = ProcessCommunicationStderrEvent
+    PROCESS_LOG = ProcessLogEvent
+    PROCESS_LOG_STDOUT = ProcessLogStdoutEvent
+    PROCESS_LOG_STDERR = ProcessLogStderrEvent
+    REMOTE_COMMUNICATION = RemoteCommunicationEvent
+    SUPERVISOR_STATE_CHANGE = SupervisorStateChangeEvent
+    SUPERVISOR_STATE_CHANGE_RUNNING = SupervisorRunningEvent
+    SUPERVISOR_STATE_CHANGE_STOPPING = SupervisorStoppingEvent
+    TICK = TickEvent
+    TICK_5 = Tick5Event
+    TICK_60 = Tick60Event
+    TICK_3600 = Tick3600Event
+    PROCESS_GROUP = ProcessGroupEvent
+    PROCESS_GROUP_ADDED = ProcessGroupAddedEvent
+    PROCESS_GROUP_REMOVED = ProcessGroupRemovedEvent
+
+
+def getEventNameByType(requested): # -> str | None:
+    ...
+
+def register(name, event): # -> None:
+    ...
diff --git a/manager/typings/supervisor/http.pyi b/manager/typings/supervisor/http.pyi
new file mode 100644 (file)
index 0000000..4f0503f
--- /dev/null
@@ -0,0 +1,216 @@
+"""
+This type stub file was generated by pyright.
+"""
+
+from supervisor.medusa import http_server
+from supervisor.medusa.auth_handler import auth_handler
+
+class NOT_DONE_YET:
+    ...
+
+
+class deferring_chunked_producer:
+    """A producer that implements the 'chunked' transfer coding for HTTP/1.1.
+    Here is a sample usage:
+            request['Transfer-Encoding'] = 'chunked'
+            request.push (
+                    producers.chunked_producer (your_producer)
+                    )
+            request.done()
+    """
+    def __init__(self, producer, footers=...) -> None:
+        ...
+    
+    def more(self): # -> Type[NOT_DONE_YET] | bytes:
+        ...
+    
+
+
+class deferring_composite_producer:
+    """combine a fifo of producers into one"""
+    def __init__(self, producers) -> None:
+        ...
+    
+    def more(self): # -> Type[NOT_DONE_YET] | Literal[b'']:
+        ...
+    
+
+
+class deferring_globbing_producer:
+    """
+    'glob' the output from a producer into a particular buffer size.
+    helps reduce the number of calls to send().  [this appears to
+    gain about 30% performance on requests to a single channel]
+    """
+    def __init__(self, producer, buffer_size=...) -> None:
+        ...
+    
+    def more(self): # -> Type[NOT_DONE_YET] | bytes:
+        ...
+    
+
+
+class deferring_hooked_producer:
+    """
+    A producer that will call <function> when it empties,.
+    with an argument of the number of bytes produced.  Useful
+    for logging/instrumentation purposes.
+    """
+    def __init__(self, producer, function) -> None:
+        ...
+    
+    def more(self): # -> Type[NOT_DONE_YET] | Literal[b'']:
+        ...
+    
+
+
+class deferring_http_request(http_server.http_request):
+    """ The medusa http_request class uses the default set of producers in
+    medusa.producers.  We can't use these because they don't know anything
+    about deferred responses, so we override various methods here.  This was
+    added to support tail -f like behavior on the logtail handler """
+    def done(self, *arg, **kw): # -> None:
+        """ I didn't want to override this, but there's no way around
+        it in order to support deferreds - CM
+
+        finalize this transaction - send output to the http channel"""
+        ...
+    
+    def log(self, bytes): # -> None:
+        """ We need to override this because UNIX domain sockets return
+        an empty string for the addr rather than a (host, port) combination """
+        ...
+    
+    def cgi_environment(self): # -> dict[Unknown, Unknown]:
+        ...
+    
+    def get_server_url(self): # -> str:
+        """ Functionality that medusa's http request doesn't have; set an
+        attribute named 'server_url' on the request based on the Host: header
+        """
+        ...
+    
+
+
+class deferring_http_channel(http_server.http_channel):
+    ac_out_buffer_size = ...
+    delay = ...
+    last_writable_check = ...
+    def writable(self, now=...): # -> bool:
+        ...
+    
+    def refill_buffer(self): # -> None:
+        """ Implement deferreds """
+        ...
+    
+    def found_terminator(self): # -> None:
+        """ We only override this to use 'deferring_http_request' class
+        instead of the normal http_request class; it sucks to need to override
+        this """
+        ...
+    
+
+
+class supervisor_http_server(http_server.http_server):
+    channel_class = deferring_http_channel
+    ip = ...
+    def prebind(self, sock, logger_object): # -> None:
+        """ Override __init__ to do logger setup earlier so it can
+        go to our logger object instead of stdout """
+        ...
+    
+    def postbind(self): # -> None:
+        ...
+    
+    def log_info(self, message, type=...): # -> None:
+        ...
+    
+
+
+class supervisor_af_inet_http_server(supervisor_http_server):
+    """ AF_INET version of supervisor HTTP server """
+    def __init__(self, ip, port, logger_object) -> None:
+        ...
+    
+
+
+class supervisor_af_unix_http_server(supervisor_http_server):
+    """ AF_UNIX version of supervisor HTTP server """
+    def __init__(self, socketname, sockchmod, sockchown, logger_object) -> None:
+        ...
+    
+    def checkused(self, socketname): # -> bool:
+        ...
+    
+
+
+class tail_f_producer:
+    def __init__(self, request, filename, head) -> None:
+        ...
+    
+    def __del__(self): # -> None:
+        ...
+    
+    def more(self): # -> bytes | Type[NOT_DONE_YET] | Literal['''==> File truncated <==
+''']:
+        ...
+    
+
+
+class logtail_handler:
+    IDENT = ...
+    path = ...
+    def __init__(self, supervisord) -> None:
+        ...
+    
+    def match(self, request):
+        ...
+    
+    def handle_request(self, request): # -> None:
+        ...
+    
+
+
+class mainlogtail_handler:
+    IDENT = ...
+    path = ...
+    def __init__(self, supervisord) -> None:
+        ...
+    
+    def match(self, request):
+        ...
+    
+    def handle_request(self, request): # -> None:
+        ...
+    
+
+
+def make_http_servers(options, supervisord): # -> list[Unknown]:
+    ...
+
+class LogWrapper:
+    '''Receives log messages from the Medusa servers and forwards
+    them to the Supervisor logger'''
+    def __init__(self, logger) -> None:
+        ...
+    
+    def log(self, msg): # -> None:
+        '''Medusa servers call this method.  There is no log level so
+        we have to sniff the message.  We want "Server Error" messages
+        from medusa.http_server logged as errors at least.'''
+        ...
+    
+
+
+class encrypted_dictionary_authorizer:
+    def __init__(self, dict) -> None:
+        ...
+    
+    def authorize(self, auth_info): # -> Literal[False]:
+        ...
+    
+
+
+class supervisor_auth_handler(auth_handler):
+    def __init__(self, dict, handler, realm=...) -> None:
+        ...
diff --git a/manager/typings/supervisor/http_client.pyi b/manager/typings/supervisor/http_client.pyi
new file mode 100644 (file)
index 0000000..94dcd82
--- /dev/null
@@ -0,0 +1,84 @@
+"""
+This type stub file was generated by pyright.
+"""
+
+from supervisor.medusa import asynchat_25 as asynchat
+
+CR = ...
+LF = ...
+CRLF = ...
+class Listener:
+    def status(self, url, status): # -> None:
+        ...
+    
+    def error(self, url, error): # -> None:
+        ...
+    
+    def response_header(self, url, name, value): # -> None:
+        ...
+    
+    def done(self, url): # -> None:
+        ...
+    
+    def feed(self, url, data): # -> None:
+        ...
+    
+    def close(self, url): # -> None:
+        ...
+    
+
+
+class HTTPHandler(asynchat.async_chat):
+    def __init__(self, listener, username=..., password=..., conn=..., map=...) -> None:
+        ...
+    
+    def get(self, serverurl, path=...): # -> None:
+        ...
+    
+    def close(self): # -> None:
+        ...
+    
+    def header(self, name, value): # -> None:
+        ...
+    
+    def handle_error(self): # -> None:
+        ...
+    
+    def handle_connect(self): # -> None:
+        ...
+    
+    def feed(self, data): # -> None:
+        ...
+    
+    def collect_incoming_data(self, bytes): # -> None:
+        ...
+    
+    def found_terminator(self): # -> None:
+        ...
+    
+    def ignore(self): # -> None:
+        ...
+    
+    def status_line(self): # -> tuple[bytes | Unknown, int, bytes | Unknown]:
+        ...
+    
+    def headers(self): # -> None:
+        ...
+    
+    def response_header(self, name, value): # -> None:
+        ...
+    
+    def body(self): # -> None:
+        ...
+    
+    def done(self): # -> None:
+        ...
+    
+    def chunked_size(self): # -> None:
+        ...
+    
+    def chunked_body(self): # -> None:
+        ...
+    
+    def trailer(self): # -> None:
+        ...
diff --git a/manager/typings/supervisor/loggers.pyi b/manager/typings/supervisor/loggers.pyi
new file mode 100644 (file)
index 0000000..b46f233
--- /dev/null
@@ -0,0 +1,233 @@
+"""
+This type stub file was generated by pyright.
+"""
+
+"""
+Logger implementation loosely modeled on PEP 282.  We don't use the
+PEP 282 logger implementation in the stdlib ('logging') because it's
+idiosyncratic and a bit slow for our purposes (we don't use threads).
+"""
+class LevelsByName:
+    CRIT = ...
+    ERRO = ...
+    WARN = ...
+    INFO = ...
+    DEBG = ...
+    TRAC = ...
+    BLAT = ...
+
+
+class LevelsByDescription:
+    critical = ...
+    error = ...
+    warn = ...
+    info = ...
+    debug = ...
+    trace = ...
+    blather = ...
+
+
+LOG_LEVELS_BY_NUM = ...
+def getLevelNumByDescription(description): # -> Any | None:
+    ...
+
+class Handler:
+    fmt = ...
+    level = ...
+    def __init__(self, stream=...) -> None:
+        ...
+    
+    def setFormat(self, fmt): # -> None:
+        ...
+    
+    def setLevel(self, level): # -> None:
+        ...
+    
+    def flush(self): # -> None:
+        ...
+    
+    def close(self): # -> None:
+        ...
+    
+    def emit(self, record): # -> None:
+        ...
+    
+    def handleError(self): # -> None:
+        ...
+    
+
+
+class StreamHandler(Handler):
+    def __init__(self, strm=...) -> None:
+        ...
+    
+    def remove(self): # -> None:
+        ...
+    
+    def reopen(self): # -> None:
+        ...
+    
+
+
+class BoundIO:
+    def __init__(self, maxbytes, buf=...) -> None:
+        ...
+    
+    def flush(self): # -> None:
+        ...
+    
+    def close(self): # -> None:
+        ...
+    
+    def write(self, b): # -> None:
+        ...
+    
+    def getvalue(self): # -> Unknown | bytes:
+        ...
+    
+    def clear(self): # -> None:
+        ...
+    
+
+
+class FileHandler(Handler):
+    """File handler which supports reopening of logs.
+    """
+    def __init__(self, filename, mode=...) -> None:
+        ...
+    
+    def reopen(self): # -> None:
+        ...
+    
+    def remove(self): # -> None:
+        ...
+    
+
+
+class RotatingFileHandler(FileHandler):
+    def __init__(self, filename, mode=..., maxBytes=..., backupCount=...) -> None:
+        """
+        Open the specified file and use it as the stream for logging.
+
+        By default, the file grows indefinitely. You can specify particular
+        values of maxBytes and backupCount to allow the file to rollover at
+        a predetermined size.
+
+        Rollover occurs whenever the current log file is nearly maxBytes in
+        length. If backupCount is >= 1, the system will successively create
+        new files with the same pathname as the base file, but with extensions
+        ".1", ".2" etc. appended to it. For example, with a backupCount of 5
+        and a base file name of "app.log", you would get "app.log",
+        "app.log.1", "app.log.2", ... through to "app.log.5". The file being
+        written to is always "app.log" - when it gets filled up, it is closed
+        and renamed to "app.log.1", and if files "app.log.1", "app.log.2" etc.
+        exist, then they are renamed to "app.log.2", "app.log.3" etc.
+        respectively.
+
+        If maxBytes is zero, rollover never occurs.
+        """
+        ...
+    
+    def emit(self, record): # -> None:
+        """
+        Emit a record.
+
+        Output the record to the file, catering for rollover as described
+        in doRollover().
+        """
+        ...
+    
+    def removeAndRename(self, sfn, dfn): # -> None:
+        ...
+    
+    def doRollover(self): # -> None:
+        """
+        Do a rollover, as described in __init__().
+        """
+        ...
+    
+
+
+class LogRecord:
+    def __init__(self, level, msg, **kw) -> None:
+        ...
+    
+    def asdict(self): # -> dict[str, str | Unknown]:
+        ...
+    
+
+
+class Logger:
+    def __init__(self, level=..., handlers=...) -> None:
+        ...
+    
+    def close(self): # -> None:
+        ...
+    
+    def blather(self, msg, **kw): # -> None:
+        ...
+    
+    def trace(self, msg, **kw): # -> None:
+        ...
+    
+    def debug(self, msg, **kw): # -> None:
+        ...
+    
+    def info(self, msg, **kw): # -> None:
+        ...
+    
+    def warn(self, msg, **kw): # -> None:
+        ...
+    
+    def error(self, msg, **kw): # -> None:
+        ...
+    
+    def critical(self, msg, **kw): # -> None:
+        ...
+    
+    def log(self, level, msg, **kw): # -> None:
+        ...
+    
+    def addHandler(self, hdlr): # -> None:
+        ...
+    
+    def getvalue(self):
+        ...
+    
+
+
+class SyslogHandler(Handler):
+    def __init__(self) -> None:
+        ...
+    
+    def close(self): # -> None:
+        ...
+    
+    def reopen(self): # -> None:
+        ...
+    
+    def emit(self, record): # -> None:
+        ...
+    
+
+
+def getLogger(level=...): # -> Logger:
+    ...
+
+_2MB = ...
+def handle_boundIO(logger, fmt, maxbytes=...): # -> None:
+    """Attach a new BoundIO handler to an existing Logger"""
+    ...
+
+def handle_stdout(logger, fmt): # -> None:
+    """Attach a new StreamHandler with stdout handler to an existing Logger"""
+    ...
+
+def handle_syslog(logger, fmt): # -> None:
+    """Attach a new Syslog handler to an existing Logger"""
+    ...
+
+def handle_file(logger, filename, fmt, rotating=..., maxbytes=..., backups=...): # -> None:
+    """Attach a new file handler to an existing Logger. If the filename
+    is the magic name of 'syslog' then make it a syslog handler instead."""
+    ...
diff --git a/manager/typings/supervisor/medusa/__init__.pyi b/manager/typings/supervisor/medusa/__init__.pyi
new file mode 100644 (file)
index 0000000..2317f9a
--- /dev/null
@@ -0,0 +1,7 @@
+"""
+This type stub file was generated by pyright.
+"""
+
+"""medusa.__init__
+"""
+__revision__ = ...
diff --git a/manager/typings/supervisor/medusa/asynchat_25.pyi b/manager/typings/supervisor/medusa/asynchat_25.pyi
new file mode 100644 (file)
index 0000000..c269d90
--- /dev/null
@@ -0,0 +1,117 @@
+"""
+This type stub file was generated by pyright.
+"""
+
+from supervisor.medusa import asyncore_25 as asyncore
+
+r"""A class supporting chat-style (command/response) protocols.
+
+This class adds support for 'chat' style protocols - where one side
+sends a 'command', and the other sends a response (examples would be
+the common internet protocols - smtp, nntp, ftp, etc..).
+
+The handle_read() method looks at the input stream for the current
+'terminator' (usually '\r\n' for single-line responses, '\r\n.\r\n'
+for multi-line output), calling self.found_terminator() on its
+receipt.
+
+for example:
+Say you build an async nntp client using this class.  At the start
+of the connection, you'll have self.terminator set to '\r\n', in
+order to process the single-line greeting.  Just before issuing a
+'LIST' command you'll set it to '\r\n.\r\n'.  The output of the LIST
+command will be accumulated (using your own 'collect_incoming_data'
+method) up to the terminator, and then control will be returned to
+you - by calling your self.found_terminator() method.
+"""
+class async_chat(asyncore.dispatcher):
+    """This is an abstract class.  You must derive from this class, and add
+    the two methods collect_incoming_data() and found_terminator()"""
+    ac_in_buffer_size = ...
+    ac_out_buffer_size = ...
+    def __init__(self, conn=..., map=...) -> None:
+        ...
+    
+    def collect_incoming_data(self, data):
+        ...
+    
+    def found_terminator(self):
+        ...
+    
+    def set_terminator(self, term): # -> None:
+        """Set the input delimiter.  Can be a fixed string of any length, an integer, or None"""
+        ...
+    
+    def get_terminator(self): # -> int:
+        ...
+    
+    def handle_read(self): # -> None:
+        ...
+    
+    def handle_write(self): # -> None:
+        ...
+    
+    def handle_close(self): # -> None:
+        ...
+    
+    def push(self, data): # -> None:
+        ...
+    
+    def push_with_producer(self, producer): # -> None:
+        ...
+    
+    def readable(self): # -> bool:
+        """predicate for inclusion in the readable for select()"""
+        ...
+    
+    def writable(self): # -> bool:
+        """predicate for inclusion in the writable for select()"""
+        ...
+    
+    def close_when_done(self): # -> None:
+        """automatically close this channel once the outgoing queue is empty"""
+        ...
+    
+    def refill_buffer(self): # -> None:
+        ...
+    
+    def initiate_send(self): # -> None:
+        ...
+    
+    def discard_buffers(self): # -> None:
+        ...
+    
+
+
+class simple_producer:
+    def __init__(self, data, buffer_size=...) -> None:
+        ...
+    
+    def more(self): # -> bytes:
+        ...
+    
+
+
+class fifo:
+    def __init__(self, list=...) -> None:
+        ...
+    
+    def __len__(self): # -> int:
+        ...
+    
+    def is_empty(self): # -> bool:
+        ...
+    
+    def first(self):
+        ...
+    
+    def push(self, data): # -> None:
+        ...
+    
+    def pop(self): # -> tuple[Literal[1], Unknown] | tuple[Literal[0], None]:
+        ...
+    
+
+
+def find_prefix_at_end(haystack, needle): # -> int:
+    ...
diff --git a/manager/typings/supervisor/medusa/asyncore_25.pyi b/manager/typings/supervisor/medusa/asyncore_25.pyi
new file mode 100644 (file)
index 0000000..d0eb145
--- /dev/null
@@ -0,0 +1,195 @@
+"""
+This type stub file was generated by pyright.
+"""
+
+import os
+
+"""Basic infrastructure for asynchronous socket service clients and servers.
+
+There are only two ways to have a program on a single processor do "more
+than one thing at a time".  Multi-threaded programming is the simplest and
+most popular way to do it, but there is another very different technique,
+that lets you have nearly all the advantages of multi-threading, without
+actually using multiple threads. it's really only practical if your program
+is largely I/O bound. If your program is CPU bound, then preemptive
+scheduled threads are probably what you really need. Network servers are
+rarely CPU-bound, however.
+
+If your operating system supports the select() system call in its I/O
+library (and nearly all do), then you can use it to juggle multiple
+communication channels at once; doing other work while your I/O is taking
+place in the "background."  Although this strategy can seem strange and
+complex, especially at first, it is in many ways easier to understand and
+control than multi-threaded programming. The module documented here solves
+many of the difficult problems for you, making the task of building
+sophisticated high-performance network servers and clients a snap.
+"""
+class ExitNow(Exception):
+    ...
+
+
+def read(obj): # -> None:
+    ...
+
+def write(obj): # -> None:
+    ...
+
+def readwrite(obj, flags): # -> None:
+    ...
+
+def poll(timeout=..., map=...): # -> None:
+    ...
+
+def poll2(timeout=..., map=...): # -> None:
+    ...
+
+poll3 = ...
+def loop(timeout=..., use_poll=..., map=..., count=...): # -> None:
+    ...
+
+class dispatcher:
+    debug = ...
+    connected = ...
+    accepting = ...
+    closing = ...
+    addr = ...
+    def __init__(self, sock=..., map=...) -> None:
+        ...
+    
+    def __repr__(self): # -> str:
+        ...
+    
+    def add_channel(self, map=...): # -> None:
+        ...
+    
+    def del_channel(self, map=...): # -> None:
+        ...
+    
+    def create_socket(self, family, type): # -> None:
+        ...
+    
+    def set_socket(self, sock, map=...): # -> None:
+        ...
+    
+    def set_reuse_addr(self): # -> None:
+        ...
+    
+    def readable(self): # -> Literal[True]:
+        ...
+    
+    def writable(self): # -> Literal[True]:
+        ...
+    
+    def listen(self, num): # -> None:
+        ...
+    
+    def bind(self, addr): # -> None:
+        ...
+    
+    def connect(self, address): # -> None:
+        ...
+    
+    def accept(self): # -> tuple[socket | Unknown, _RetAddress | Unknown] | None:
+        ...
+    
+    def send(self, data): # -> int:
+        ...
+    
+    def recv(self, buffer_size): # -> bytes:
+        ...
+    
+    def close(self): # -> None:
+        ...
+    
+    def __getattr__(self, attr): # -> Any:
+        ...
+    
+    def log(self, message): # -> None:
+        ...
+    
+    def log_info(self, message, type=...): # -> None:
+        ...
+    
+    def handle_read_event(self): # -> None:
+        ...
+    
+    def handle_write_event(self): # -> None:
+        ...
+    
+    def handle_expt_event(self): # -> None:
+        ...
+    
+    def handle_error(self): # -> None:
+        ...
+    
+    def handle_expt(self): # -> None:
+        ...
+    
+    def handle_read(self): # -> None:
+        ...
+    
+    def handle_write(self): # -> None:
+        ...
+    
+    def handle_connect(self): # -> None:
+        ...
+    
+    def handle_accept(self): # -> None:
+        ...
+    
+    def handle_close(self): # -> None:
+        ...
+    
+
+
+class dispatcher_with_send(dispatcher):
+    def __init__(self, sock=..., map=...) -> None:
+        ...
+    
+    def initiate_send(self): # -> None:
+        ...
+    
+    def handle_write(self): # -> None:
+        ...
+    
+    def writable(self): # -> int | Literal[True]:
+        ...
+    
+    def send(self, data): # -> None:
+        ...
+    
+
+
+def compact_traceback(): # -> tuple[tuple[Unknown, Unknown, Unknown], Type[BaseException] | None, BaseException | None, str]:
+    ...
+
+def close_all(map=...): # -> None:
+    ...
+
+if os.name == 'posix':
+    class file_wrapper:
+        def __init__(self, fd) -> None:
+            ...
+        
+        def recv(self, buffersize): # -> str:
+            ...
+        
+        def send(self, s): # -> int:
+            ...
+        
+        read = ...
+        write = ...
+        def close(self): # -> None:
+            ...
+        
+        def fileno(self):
+            ...
+        
+    
+    
+    class file_dispatcher(dispatcher):
+        def __init__(self, fd, map=...) -> None:
+            ...
+        
+        def set_file(self, fd): # -> None:
+            ...
diff --git a/manager/typings/supervisor/medusa/auth_handler.pyi b/manager/typings/supervisor/medusa/auth_handler.pyi
new file mode 100644 (file)
index 0000000..8c19e35
--- /dev/null
@@ -0,0 +1,42 @@
+"""
+This type stub file was generated by pyright.
+"""
+
+RCS_ID = ...
+get_header = ...
+class auth_handler:
+    def __init__(self, dict, handler, realm=...) -> None:
+        ...
+    
+    def match(self, request):
+        ...
+    
+    def handle_request(self, request): # -> None:
+        ...
+    
+    def handle_unauthorized(self, request): # -> None:
+        ...
+    
+    def make_nonce(self, request): # -> bytes:
+        """A digest-authentication <nonce>, constructed as suggested in RFC 2069"""
+        ...
+    
+    def apply_hash(self, s): # -> bytes:
+        """Apply MD5 to a string <s>, then wrap it in base64 encoding."""
+        ...
+    
+    def status(self): # -> composite_producer:
+        ...
+    
+
+
+class dictionary_authorizer:
+    def __init__(self, dict) -> None:
+        ...
+    
+    def authorize(self, auth_info): # -> Literal[1, 0]:
+        ...
+    
+
+
+AUTHORIZATION = ...
diff --git a/manager/typings/supervisor/medusa/counter.pyi b/manager/typings/supervisor/medusa/counter.pyi
new file mode 100644 (file)
index 0000000..f540230
--- /dev/null
@@ -0,0 +1,27 @@
+"""
+This type stub file was generated by pyright.
+"""
+
+class counter:
+    """general-purpose counter"""
+    def __init__(self, initial_value=...) -> None:
+        ...
+    
+    def increment(self, delta=...): # -> Unknown:
+        ...
+    
+    def decrement(self, delta=...): # -> Unknown:
+        ...
+    
+    def as_long(self): # -> int:
+        ...
+    
+    def __nonzero__(self):
+        ...
+    
+    __bool__ = ...
+    def __repr__(self): # -> str:
+        ...
+    
+    def __str__(self) -> str:
+        ...
diff --git a/manager/typings/supervisor/medusa/default_handler.pyi b/manager/typings/supervisor/medusa/default_handler.pyi
new file mode 100644 (file)
index 0000000..227e0da
--- /dev/null
@@ -0,0 +1,41 @@
+"""
+This type stub file was generated by pyright.
+"""
+
+import supervisor.medusa.producers as producers
+
+RCS_ID = ...
+unquote = ...
+class default_handler:
+    valid_commands = ...
+    IDENT = ...
+    directory_defaults = ...
+    default_file_producer = producers.file_producer
+    def __init__(self, filesystem) -> None:
+        ...
+    
+    hit_counter = ...
+    def __repr__(self): # -> str:
+        ...
+    
+    def match(self, request): # -> Literal[1]:
+        ...
+    
+    def handle_request(self, request): # -> None:
+        ...
+    
+    def set_content_type(self, path, request): # -> None:
+        ...
+    
+    def status(self): # -> simple_producer:
+        ...
+    
+
+
+IF_MODIFIED_SINCE = ...
+USER_AGENT = ...
+CONTENT_TYPE = ...
+get_header = ...
+get_header_match = ...
+def get_extension(path): # -> Literal['']:
+    ...
diff --git a/manager/typings/supervisor/medusa/filesys.pyi b/manager/typings/supervisor/medusa/filesys.pyi
new file mode 100644 (file)
index 0000000..7f0ffdb
--- /dev/null
@@ -0,0 +1,176 @@
+"""
+This type stub file was generated by pyright.
+"""
+
+import os
+
+class abstract_filesystem:
+    def __init__(self) -> None:
+        ...
+    
+    def current_directory(self): # -> None:
+        """Return a string representing the current directory."""
+        ...
+    
+    def listdir(self, path, long=...): # -> None:
+        """Return a listing of the directory at 'path' The empty string
+        indicates the current directory.  If 'long' is set, instead
+        return a list of (name, stat_info) tuples
+        """
+        ...
+    
+    def open(self, path, mode): # -> None:
+        """Return an open file object"""
+        ...
+    
+    def stat(self, path): # -> None:
+        """Return the equivalent of os.stat() on the given path."""
+        ...
+    
+    def isdir(self, path): # -> None:
+        """Does the path represent a directory?"""
+        ...
+    
+    def isfile(self, path): # -> None:
+        """Does the path represent a plain file?"""
+        ...
+    
+    def cwd(self, path): # -> None:
+        """Change the working directory."""
+        ...
+    
+    def cdup(self): # -> None:
+        """Change to the parent of the current directory."""
+        ...
+    
+    def longify(self, path): # -> None:
+        """Return a 'long' representation of the filename
+        [for the output of the LIST command]"""
+        ...
+    
+
+
+def safe_stat(path): # -> tuple[Unknown, stat_result] | None:
+    ...
+
+class os_filesystem:
+    path_module = os.path
+    do_globbing = ...
+    def __init__(self, root, wd=...) -> None:
+        ...
+    
+    def current_directory(self): # -> Unknown | str:
+        ...
+    
+    def isfile(self, path): # -> bool:
+        ...
+    
+    def isdir(self, path): # -> bool:
+        ...
+    
+    def cwd(self, path): # -> Literal[0, 1]:
+        ...
+    
+    def cdup(self): # -> Literal[0, 1]:
+        ...
+    
+    def listdir(self, path, long=...): # -> list_producer:
+        ...
+    
+    def stat(self, path): # -> stat_result:
+        ...
+    
+    def open(self, path, mode): # -> TextIOWrapper:
+        ...
+    
+    def unlink(self, path): # -> None:
+        ...
+    
+    def mkdir(self, path): # -> None:
+        ...
+    
+    def rmdir(self, path): # -> None:
+        ...
+    
+    def rename(self, src, dst): # -> None:
+        ...
+    
+    def normalize(self, path): # -> str:
+        ...
+    
+    def translate(self, path): # -> str:
+        ...
+    
+    def longify(self, path_stat_info_tuple): # -> str:
+        ...
+    
+    def __repr__(self): # -> str:
+        ...
+    
+
+
+if os.name == 'posix':
+    class unix_filesystem(os_filesystem):
+        ...
+    
+    
+    class schizophrenic_unix_filesystem(os_filesystem):
+        PROCESS_UID = ...
+        PROCESS_EUID = ...
+        PROCESS_GID = ...
+        PROCESS_EGID = ...
+        def __init__(self, root, wd=..., persona=...) -> None:
+            ...
+        
+        def become_persona(self): # -> None:
+            ...
+        
+        def become_nobody(self): # -> None:
+            ...
+        
+        def cwd(self, path): # -> Literal[0, 1]:
+            ...
+        
+        def cdup(self): # -> Literal[0, 1]:
+            ...
+        
+        def open(self, filename, mode): # -> TextIOWrapper:
+            ...
+        
+        def listdir(self, path, long=...): # -> list_producer:
+            ...
+        
+    
+    
+class msdos_filesystem(os_filesystem):
+    def longify(self, path_stat_info_tuple): # -> str:
+        ...
+    
+
+
+class merged_filesystem:
+    def __init__(self, *fsys) -> None:
+        ...
+    
+
+
+def msdos_longify(file, stat_info): # -> str:
+    ...
+
+def msdos_date(t): # -> str:
+    ...
+
+months = ...
+mode_table = ...
+def unix_longify(file, stat_info): # -> str:
+    ...
+
+def ls_date(now, t): # -> str:
+    ...
+
+class list_producer:
+    def __init__(self, list, func=...) -> None:
+        ...
+    
+    def more(self): # -> str:
+        ...
diff --git a/manager/typings/supervisor/medusa/http_date.pyi b/manager/typings/supervisor/medusa/http_date.pyi
new file mode 100644 (file)
index 0000000..1aa9fa0
--- /dev/null
@@ -0,0 +1,37 @@
+"""
+This type stub file was generated by pyright.
+"""
+
+def concat(*args): # -> str:
+    ...
+
+def join(seq, field=...):
+    ...
+
+def group(s):
+    ...
+
+short_days = ...
+long_days = ...
+short_day_reg = ...
+long_day_reg = ...
+daymap = ...
+hms_reg = ...
+months = ...
+monmap = ...
+months_reg = ...
+rfc822_date = ...
+rfc822_reg = ...
+def unpack_rfc822(m): # -> tuple[int, Unknown, int, int, int, int, Literal[0], Literal[0], Literal[0]]:
+    ...
+
+rfc850_date = ...
+rfc850_reg = ...
+def unpack_rfc850(m): # -> tuple[int, Unknown, int, int, int, int, Literal[0], Literal[0], Literal[0]]:
+    ...
+
+def build_http_date(when): # -> str:
+    ...
+
+def parse_http_date(d): # -> int:
+    ...
diff --git a/manager/typings/supervisor/medusa/http_server.pyi b/manager/typings/supervisor/medusa/http_server.pyi
new file mode 100644 (file)
index 0000000..0a3ee62
--- /dev/null
@@ -0,0 +1,196 @@
+"""
+This type stub file was generated by pyright.
+"""
+
+import supervisor.medusa.asynchat_25 as asynchat
+import supervisor.medusa.asyncore_25 as asyncore
+
+RCS_ID = ...
+VERSION_STRING = ...
+class http_request:
+    reply_code = ...
+    request_counter = ...
+    use_chunked = ...
+    collector = ...
+    def __init__(self, *args) -> None:
+        ...
+    
+    def __setitem__(self, key, value): # -> None:
+        ...
+    
+    def __getitem__(self, key): # -> str:
+        ...
+    
+    def __contains__(self, key): # -> bool:
+        ...
+    
+    def has_key(self, key): # -> bool:
+        ...
+    
+    def build_reply_header(self): # -> bytes:
+        ...
+    
+    def add_header(self, name, value): # -> None:
+        """ Adds a header to the reply headers """
+        ...
+    
+    def clear_headers(self): # -> None:
+        """ Clears the reply header list """
+        ...
+    
+    def remove_header(self, name, value=...): # -> None:
+        """ Removes the specified header.
+        If a value is provided, the name and
+        value must match to remove the header.
+        If the value is None, removes all headers
+        with that name."""
+        ...
+    
+    def get_reply_headers(self): # -> List[Unknown]:
+        """ Get the tuple of headers that will be used
+        for generating reply headers"""
+        ...
+    
+    def get_reply_header_text(self): # -> str:
+        """ Gets the reply header (including status and
+        additional crlf)"""
+        ...
+    
+    path_regex = ...
+    def split_uri(self): # -> Tuple[str | Any, ...]:
+        ...
+    
+    def get_header_with_regex(self, head_reg, group): # -> Literal['']:
+        ...
+    
+    def get_header(self, header): # -> None:
+        ...
+    
+    def collect_incoming_data(self, data): # -> None:
+        ...
+    
+    def found_terminator(self): # -> None:
+        ...
+    
+    def push(self, thing): # -> None:
+        ...
+    
+    def response(self, code=...): # -> str:
+        ...
+    
+    def error(self, code): # -> None:
+        ...
+    
+    reply_now = ...
+    def done(self): # -> None:
+        """finalize this transaction - send output to the http channel"""
+        ...
+    
+    def log_date_string(self, when): # -> str:
+        ...
+    
+    def log(self, bytes): # -> None:
+        ...
+    
+    responses = ...
+    DEFAULT_ERROR_MESSAGE = ...
+    def log_info(self, msg, level): # -> None:
+        ...
+    
+
+
+class http_channel(asynchat.async_chat):
+    ac_out_buffer_size = ...
+    current_request = ...
+    channel_counter = ...
+    def __init__(self, server, conn, addr) -> None:
+        ...
+    
+    def __repr__(self): # -> str:
+        ...
+    
+    maintenance_interval = ...
+    def check_maintenance(self): # -> None:
+        ...
+    
+    def maintenance(self): # -> None:
+        ...
+    
+    zombie_timeout = ...
+    def kill_zombies(self): # -> None:
+        ...
+    
+    def send(self, data): # -> int:
+        ...
+    
+    def recv(self, buffer_size): # -> bytes:
+        ...
+    
+    def handle_error(self): # -> None:
+        ...
+    
+    def log(self, *args): # -> None:
+        ...
+    
+    def collect_incoming_data(self, data): # -> None:
+        ...
+    
+    def found_terminator(self): # -> None:
+        ...
+    
+    def writable_for_proxy(self): # -> bool | Literal[1] | None:
+        ...
+    
+
+
+class http_server(asyncore.dispatcher):
+    SERVER_IDENT = ...
+    channel_class = http_channel
+    def __init__(self, ip, port, resolver=..., logger_object=...) -> None:
+        ...
+    
+    def writable(self): # -> Literal[0]:
+        ...
+    
+    def handle_read(self): # -> None:
+        ...
+    
+    def readable(self): # -> bool:
+        ...
+    
+    def handle_connect(self): # -> None:
+        ...
+    
+    def handle_accept(self): # -> None:
+        ...
+    
+    def install_handler(self, handler, back=...): # -> None:
+        ...
+    
+    def remove_handler(self, handler): # -> None:
+        ...
+    
+    def status(self): # -> composite_producer:
+        ...
+    
+
+
+def maybe_status(thing): # -> None:
+    ...
+
+CONNECTION = ...
+def join_headers(headers): # -> list[Unknown]:
+    ...
+
+def get_header(head_reg, lines, group=...): # -> Literal['']:
+    ...
+
+def get_header_match(head_reg, lines): # -> Literal['']:
+    ...
+
+REQUEST = ...
+def crack_request(r): # -> tuple[str | Any, str | Any, str | Any | None] | tuple[None, None, None]:
+    ...
+
+if __name__ == '__main__':
+    ...
diff --git a/manager/typings/supervisor/medusa/logger.pyi b/manager/typings/supervisor/medusa/logger.pyi
new file mode 100644 (file)
index 0000000..2a2a9d0
--- /dev/null
@@ -0,0 +1,122 @@
+"""
+This type stub file was generated by pyright.
+"""
+
+import supervisor.medusa.asynchat_25 as asynchat
+
+class file_logger:
+    def __init__(self, file, flush=..., mode=...) -> None:
+        ...
+    
+    def __repr__(self): # -> str:
+        ...
+    
+    def write(self, data): # -> None:
+        ...
+    
+    def writeline(self, line): # -> None:
+        ...
+    
+    def writelines(self, lines): # -> None:
+        ...
+    
+    def maybe_flush(self): # -> None:
+        ...
+    
+    def flush(self): # -> None:
+        ...
+    
+    def softspace(self, *args): # -> None:
+        ...
+    
+    def log(self, message): # -> None:
+        ...
+    
+
+
+class rotating_file_logger(file_logger):
+    def __init__(self, file, freq=..., maxsize=..., flush=..., mode=...) -> None:
+        ...
+    
+    def __repr__(self): # -> str:
+        ...
+    
+    def next_backup(self, freq): # -> float | None:
+        ...
+    
+    def maybe_flush(self): # -> None:
+        ...
+    
+    def maybe_rotate(self): # -> None:
+        ...
+    
+    def rotate(self): # -> None:
+        ...
+    
+
+
+class socket_logger(asynchat.async_chat):
+    def __init__(self, address) -> None:
+        ...
+    
+    def __repr__(self): # -> str:
+        ...
+    
+    def log(self, message): # -> None:
+        ...
+    
+
+
+class multi_logger:
+    def __init__(self, loggers) -> None:
+        ...
+    
+    def __repr__(self): # -> str:
+        ...
+    
+    def log(self, message): # -> None:
+        ...
+    
+
+
+class resolving_logger:
+    """Feed (ip, message) combinations into this logger to get a
+    resolved hostname in front of the message.  The message will not
+    be logged until the PTR request finishes (or fails)."""
+    def __init__(self, resolver, logger) -> None:
+        ...
+    
+    class logger_thunk:
+        def __init__(self, message, logger) -> None:
+            ...
+        
+        def __call__(self, host, ttl, answer): # -> None:
+            ...
+        
+    
+    
+    def log(self, ip, message): # -> None:
+        ...
+    
+
+
+class unresolving_logger:
+    """Just in case you don't want to resolve"""
+    def __init__(self, logger) -> None:
+        ...
+    
+    def log(self, ip, message): # -> None:
+        ...
+    
+
+
+def strip_eol(line):
+    ...
+
+class tail_logger:
+    """Keep track of the last <size> log messages"""
+    def __init__(self, logger, size=...) -> None:
+        ...
+    
+    def log(self, message): # -> None:
+        ...
diff --git a/manager/typings/supervisor/medusa/producers.pyi b/manager/typings/supervisor/medusa/producers.pyi
new file mode 100644 (file)
index 0000000..00a005d
--- /dev/null
@@ -0,0 +1,155 @@
+"""
+This type stub file was generated by pyright.
+"""
+
+RCS_ID = ...
+class simple_producer:
+    """producer for a string"""
+    def __init__(self, data, buffer_size=...) -> None:
+        ...
+    
+    def more(self): # -> bytes | Unknown:
+        ...
+    
+
+
+class scanning_producer:
+    """like simple_producer, but more efficient for large strings"""
+    def __init__(self, data, buffer_size=...) -> None:
+        ...
+    
+    def more(self): # -> Literal[b'']:
+        ...
+    
+
+
+class lines_producer:
+    """producer for a list of lines"""
+    def __init__(self, lines) -> None:
+        ...
+    
+    def more(self): # -> str:
+        ...
+    
+
+
+class buffer_list_producer:
+    """producer for a list of strings"""
+    def __init__(self, buffers) -> None:
+        ...
+    
+    def more(self): # -> Literal[b'']:
+        ...
+    
+
+
+class file_producer:
+    """producer wrapper for file[-like] objects"""
+    out_buffer_size = ...
+    def __init__(self, file) -> None:
+        ...
+    
+    def more(self): # -> Literal[b'']:
+        ...
+    
+
+
+class output_producer:
+    """Acts like an output file; suitable for capturing sys.stdout"""
+    def __init__(self) -> None:
+        ...
+    
+    def write(self, data): # -> None:
+        ...
+    
+    def writeline(self, line): # -> None:
+        ...
+    
+    def writelines(self, lines): # -> None:
+        ...
+    
+    def flush(self): # -> None:
+        ...
+    
+    def softspace(self, *args): # -> None:
+        ...
+    
+    def more(self): # -> bytes | Literal['']:
+        ...
+    
+
+
+class composite_producer:
+    """combine a fifo of producers into one"""
+    def __init__(self, producers) -> None:
+        ...
+    
+    def more(self): # -> Literal[b'']:
+        ...
+    
+
+
+class globbing_producer:
+    """
+    'glob' the output from a producer into a particular buffer size.
+    helps reduce the number of calls to send().  [this appears to
+    gain about 30% performance on requests to a single channel]
+    """
+    def __init__(self, producer, buffer_size=...) -> None:
+        ...
+    
+    def more(self): # -> bytes:
+        ...
+    
+
+
+class hooked_producer:
+    """
+    A producer that will call <function> when it empties,.
+    with an argument of the number of bytes produced.  Useful
+    for logging/instrumentation purposes.
+    """
+    def __init__(self, producer, function) -> None:
+        ...
+    
+    def more(self): # -> Literal['']:
+        ...
+    
+
+
+class chunked_producer:
+    """A producer that implements the 'chunked' transfer coding for HTTP/1.1.
+    Here is a sample usage:
+            request['Transfer-Encoding'] = 'chunked'
+            request.push (
+                    producers.chunked_producer (your_producer)
+                    )
+            request.done()
+    """
+    def __init__(self, producer, footers=...) -> None:
+        ...
+    
+    def more(self): # -> bytes:
+        ...
+    
+
+
+class compressed_producer:
+    """
+    Compress another producer on-the-fly, using ZLIB
+    """
+    def __init__(self, producer, level=...) -> None:
+        ...
+    
+    def more(self): # -> bytes:
+        ...
+    
+
+
+class escaping_producer:
+    """A producer that escapes a sequence of characters"""
+    def __init__(self, producer, esc_from=..., esc_to=...) -> None:
+        ...
+    
+    def more(self):
+        ...
diff --git a/manager/typings/supervisor/medusa/util.pyi b/manager/typings/supervisor/medusa/util.pyi
new file mode 100644 (file)
index 0000000..9d54a35
--- /dev/null
@@ -0,0 +1,18 @@
+"""
+This type stub file was generated by pyright.
+"""
+
+def html_repr(object): # -> str:
+    ...
+
+def progressive_divide(n, parts): # -> list[Unknown]:
+    ...
+
+def split_by_units(n, units, dividers, format_string): # -> list[Unknown]:
+    ...
+
+def english_bytes(n): # -> list[str]:
+    ...
+
+def english_time(n): # -> list[str]:
+    ...
diff --git a/manager/typings/supervisor/medusa/xmlrpc_handler.pyi b/manager/typings/supervisor/medusa/xmlrpc_handler.pyi
new file mode 100644 (file)
index 0000000..52e60dc
--- /dev/null
@@ -0,0 +1,42 @@
+"""
+This type stub file was generated by pyright.
+"""
+
+VERSION = ...
+class xmlrpc_handler:
+    def match(self, request): # -> Literal[1, 0]:
+        ...
+    
+    def handle_request(self, request): # -> None:
+        ...
+    
+    def continue_request(self, data, request): # -> None:
+        ...
+    
+    def call(self, method, params): # -> NoReturn:
+        ...
+    
+
+
+class collector:
+    """gathers input for POST and PUT requests"""
+    def __init__(self, handler, request) -> None:
+        ...
+    
+    def collect_incoming_data(self, data): # -> None:
+        ...
+    
+    def found_terminator(self): # -> None:
+        ...
+    
+
+
+if __name__ == '__main__':
+    class rpc_demo(xmlrpc_handler):
+        def call(self, method, params): # -> Literal['Sure, that works']:
+            ...
+        
+    
+    
+    hs = ...
+    rpc = ...
diff --git a/manager/typings/supervisor/options.pyi b/manager/typings/supervisor/options.pyi
new file mode 100644 (file)
index 0000000..3363b96
--- /dev/null
@@ -0,0 +1,527 @@
+"""
+This type stub file was generated by pyright.
+"""
+
+import warnings
+
+from supervisor.compat import ConfigParser
+
+VERSION = ...
+def normalize_path(v):
+    ...
+
+class Dummy:
+    ...
+
+
+class Options:
+    stderr = ...
+    stdout = ...
+    exit = ...
+    warnings = warnings
+    uid = ...
+    progname = ...
+    configfile = ...
+    schemadir = ...
+    configroot = ...
+    here = ...
+    positional_args_allowed = ...
+    def __init__(self, require_configfile=...) -> None:
+        """Constructor.
+
+        Params:
+        require_configfile -- whether we should fail on no config file.
+        """
+        ...
+    
+    def default_configfile(self): # -> str | None:
+        """Return the name of the found config file or print usage/exit."""
+        ...
+    
+    def help(self, dummy): # -> None:
+        """Print a long help message to stdout and exit(0).
+
+        Occurrences of "%s" in are replaced by self.progname.
+        """
+        ...
+    
+    def usage(self, msg): # -> None:
+        """Print a brief error message to stderr and exit(2)."""
+        ...
+    
+    def add(self, name=..., confname=..., short=..., long=..., handler=..., default=..., required=..., flag=..., env=...): # -> None:
+        """Add information about a configuration option.
+
+        This can take several forms:
+
+        add(name, confname)
+            Configuration option 'confname' maps to attribute 'name'
+        add(name, None, short, long)
+            Command line option '-short' or '--long' maps to 'name'
+        add(None, None, short, long, handler)
+            Command line option calls handler
+        add(name, None, short, long, handler)
+            Assign handler return value to attribute 'name'
+
+        In addition, one of the following keyword arguments may be given:
+
+        default=...  -- if not None, the default value
+        required=... -- if nonempty, an error message if no value provided
+        flag=...     -- if not None, flag value for command line option
+        env=...      -- if not None, name of environment variable that
+                        overrides the configuration file or default
+        """
+        ...
+    
+    def realize(self, args=..., doc=..., progname=...): # -> None:
+        """Realize a configuration.
+
+        Optional arguments:
+
+        args     -- the command line arguments, less the program name
+                    (default is sys.argv[1:])
+
+        doc      -- usage message (default is __main__.__doc__)
+        """
+        ...
+    
+    def process_config(self, do_usage=...): # -> None:
+        """Process configuration data structure.
+
+        This includes reading config file if necessary, setting defaults etc.
+        """
+        ...
+    
+    def process_config_file(self, do_usage): # -> None:
+        ...
+    
+    def exists(self, path): # -> bool:
+        ...
+    
+    def open(self, fn, mode=...): # -> TextIOWrapper:
+        ...
+    
+    def get_plugins(self, parser, factory_key, section_prefix): # -> list[Unknown]:
+        ...
+    
+    def import_spec(self, spec): # -> Any:
+        ...
+    
+
+
+class ServerOptions(Options):
+    user = ...
+    sockchown = ...
+    sockchmod = ...
+    logfile = ...
+    loglevel = ...
+    pidfile = ...
+    passwdfile = ...
+    nodaemon = ...
+    silent = ...
+    httpservers = ...
+    unlink_pidfile = ...
+    unlink_socketfiles = ...
+    mood = ...
+    def __init__(self) -> None:
+        ...
+    
+    def version(self, dummy): # -> None:
+        """Print version to stdout and exit(0).
+        """
+        ...
+    
+    def getLogger(self, *args, **kwargs): # -> Logger:
+        ...
+    
+    def default_configfile(self): # -> str | None:
+        ...
+    
+    def realize(self, *arg, **kw): # -> None:
+        ...
+    
+    def process_config(self, do_usage=...): # -> None:
+        ...
+    
+    def read_config(self, fp):
+        ...
+    
+    def process_groups_from_parser(self, parser): # -> list[Unknown]:
+        ...
+    
+    def parse_fcgi_socket(self, sock, proc_uid, socket_owner, socket_mode, socket_backlog): # -> UnixStreamSocketConfig | InetStreamSocketConfig:
+        ...
+    
+    def processes_from_section(self, parser, section, group_name, klass=...): # -> list[Unknown]:
+        ...
+    
+    def server_configs_from_parser(self, parser): # -> list[Unknown]:
+        ...
+    
+    def daemonize(self): # -> None:
+        ...
+    
+    def write_pidfile(self): # -> None:
+        ...
+    
+    def cleanup(self): # -> None:
+        ...
+    
+    def close_httpservers(self): # -> None:
+        ...
+    
+    def close_logger(self): # -> None:
+        ...
+    
+    def setsignals(self): # -> None:
+        ...
+    
+    def get_signal(self): # -> None:
+        ...
+    
+    def openhttpservers(self, supervisord): # -> None:
+        ...
+    
+    def get_autochildlog_name(self, name, identifier, channel):
+        ...
+    
+    def clear_autochildlogdir(self): # -> None:
+        ...
+    
+    def get_socket_map(self): # -> dict[Unknown, Unknown]:
+        ...
+    
+    def cleanup_fds(self): # -> None:
+        ...
+    
+    def kill(self, pid, signal): # -> None:
+        ...
+    
+    def waitpid(self): # -> tuple[int | None, int | None]:
+        ...
+    
+    def drop_privileges(self, user): # -> str | None:
+        """Drop privileges to become the specified user, which may be a
+        username or uid.  Called for supervisord startup and when spawning
+        subprocesses.  Returns None on success or a string error message if
+        privileges could not be dropped."""
+        ...
+    
+    def set_uid_or_exit(self): # -> None:
+        """Set the uid of the supervisord process.  Called during supervisord
+        startup only.  No return value.  Exits the process via usage() if
+        privileges could not be dropped."""
+        ...
+    
+    def set_rlimits_or_exit(self): # -> None:
+        """Set the rlimits of the supervisord process.  Called during
+        supervisord startup only.  No return value.  Exits the process via
+        usage() if any rlimits could not be set."""
+        ...
+    
+    def make_logger(self): # -> None:
+        ...
+    
+    def make_http_servers(self, supervisord): # -> list[Unknown]:
+        ...
+    
+    def close_fd(self, fd): # -> None:
+        ...
+    
+    def fork(self): # -> int:
+        ...
+    
+    def dup2(self, frm, to): # -> None:
+        ...
+    
+    def setpgrp(self): # -> None:
+        ...
+    
+    def stat(self, filename): # -> stat_result:
+        ...
+    
+    def write(self, fd, data): # -> int:
+        ...
+    
+    def execve(self, filename, argv, env): # -> NoReturn:
+        ...
+    
+    def mktempfile(self, suffix, prefix, dir):
+        ...
+    
+    def remove(self, path): # -> None:
+        ...
+    
+    def setumask(self, mask): # -> None:
+        ...
+    
+    def get_path(self): # -> List[str]:
+        """Return a list corresponding to $PATH, or a default."""
+        ...
+    
+    def get_pid(self): # -> int:
+        ...
+    
+    def check_execv_args(self, filename, argv, st): # -> None:
+        ...
+    
+    def reopenlogs(self): # -> None:
+        ...
+    
+    def readfd(self, fd): # -> bytes:
+        ...
+    
+    def chdir(self, dir): # -> None:
+        ...
+    
+    def make_pipes(self, stderr=...): # -> dict[str, None]:
+        """ Create pipes for parent to child stdin/stdout/stderr
+        communications.  Open fd in non-blocking mode so we can read them
+        in the mainloop without blocking.  If stderr is False, don't
+        create a pipe for stderr. """
+        ...
+    
+    def close_parent_pipes(self, pipes): # -> None:
+        ...
+    
+    def close_child_pipes(self, pipes): # -> None:
+        ...
+    
+
+
+class ClientOptions(Options):
+    positional_args_allowed = ...
+    interactive = ...
+    prompt = ...
+    serverurl = ...
+    username = ...
+    password = ...
+    history_file = ...
+    def __init__(self) -> None:
+        ...
+    
+    def realize(self, *arg, **kw): # -> None:
+        ...
+    
+    def read_config(self, fp):
+        ...
+    
+    def getServerProxy(self):
+        ...
+    
+
+
+_marker = ...
+class UnhosedConfigParser(ConfigParser.RawConfigParser):
+    mysection = ...
+    def __init__(self, *args, **kwargs) -> None:
+        ...
+    
+    def read_string(self, string, source=...): # -> None:
+        '''Parse configuration data from a string.  This is intended
+        to be used in tests only.  We add this method for Py 2/3 compat.'''
+        ...
+    
+    def read(self, filenames, **kwargs): # -> list[Unknown]:
+        '''Attempt to read and parse a list of filenames, returning a list
+        of filenames which were successfully parsed.  This is a method of
+        RawConfigParser that is overridden to build self.section_to_file,
+        which is a mapping of section names to the files they came from.
+        '''
+        ...
+    
+    def saneget(self, section, option, default=..., do_expand=..., expansions=...): # -> str:
+        ...
+    
+    def getdefault(self, option, default=..., expansions=..., **kwargs): # -> str:
+        ...
+    
+    def expand_here(self, here): # -> None:
+        ...
+    
+
+
+class Config:
+    def __ne__(self, other) -> bool:
+        ...
+    
+    def __lt__(self, other) -> bool:
+        ...
+    
+    def __le__(self, other) -> bool:
+        ...
+    
+    def __gt__(self, other) -> bool:
+        ...
+    
+    def __ge__(self, other) -> bool:
+        ...
+    
+    def __repr__(self): # -> str:
+        ...
+    
+
+
+class ProcessConfig(Config):
+    req_param_names = ...
+    optional_param_names = ...
+    def __init__(self, options, **params) -> None:
+        ...
+    
+    def __eq__(self, other) -> bool:
+        ...
+    
+    def get_path(self):
+        '''Return a list corresponding to $PATH that is configured to be set
+        in the process environment, or the system default.'''
+        ...
+    
+    def create_autochildlogs(self): # -> None:
+        ...
+    
+    def make_process(self, group=...): # -> Subprocess:
+        ...
+    
+    def make_dispatchers(self, proc): # -> tuple[dict[Unknown, Unknown], Unknown]:
+        ...
+    
+
+
+class EventListenerConfig(ProcessConfig):
+    def make_dispatchers(self, proc): # -> tuple[dict[Unknown, Unknown], Unknown]:
+        ...
+    
+
+
+class FastCGIProcessConfig(ProcessConfig):
+    def make_process(self, group=...): # -> FastCGISubprocess:
+        ...
+    
+    def make_dispatchers(self, proc): # -> tuple[dict[Unknown, Unknown], Unknown]:
+        ...
+    
+
+
+class ProcessGroupConfig(Config):
+    def __init__(self, options, name, priority, process_configs) -> None:
+        ...
+    
+    def __eq__(self, other) -> bool:
+        ...
+    
+    def after_setuid(self): # -> None:
+        ...
+    
+    def make_group(self): # -> ProcessGroup:
+        ...
+    
+
+
+class EventListenerPoolConfig(Config):
+    def __init__(self, options, name, priority, process_configs, buffer_size, pool_events, result_handler) -> None:
+        ...
+    
+    def __eq__(self, other) -> bool:
+        ...
+    
+    def after_setuid(self): # -> None:
+        ...
+    
+    def make_group(self): # -> EventListenerPool:
+        ...
+    
+
+
+class FastCGIGroupConfig(ProcessGroupConfig):
+    def __init__(self, options, name, priority, process_configs, socket_config) -> None:
+        ...
+    
+    def __eq__(self, other) -> bool:
+        ...
+    
+    def make_group(self): # -> FastCGIProcessGroup:
+        ...
+    
+
+
+def readFile(filename, offset, length): # -> bytes:
+    """ Read length bytes from the file named by filename starting at
+    offset """
+    ...
+
+def tailFile(filename, offset, length): # -> list[str | int | bool] | list[str | Unknown | bool]:
+    """
+    Read length bytes from the file named by filename starting at
+    offset, automatically increasing offset and setting overflow
+    flag if log size has grown beyond (offset + length).  If length
+    bytes are not available, as many bytes as are available are returned.
+    """
+    ...
+
+def decode_wait_status(sts): # -> tuple[int, str] | tuple[Literal[-1], Unknown | str] | tuple[Literal[-1], Unknown]:
+    """Decode the status returned by wait() or waitpid().
+
+    Return a tuple (exitstatus, message) where exitstatus is the exit
+    status, or -1 if the process was killed by a signal; and message
+    is a message telling what happened.  It is the caller's
+    responsibility to display the message.
+    """
+    ...
+
+_signames = ...
+def signame(sig):
+    """Return a symbolic name for a signal.
+
+    Return "signal NNN" if there is no corresponding SIG name in the
+    signal module.
+    """
+    ...
+
+class SignalReceiver:
+    def __init__(self) -> None:
+        ...
+    
+    def receive(self, sig, frame): # -> None:
+        ...
+    
+    def get_signal(self): # -> None:
+        ...
+    
+
+
+def expand(s, expansions, name):
+    ...
+
+def make_namespec(group_name, process_name): # -> str:
+    ...
+
+def split_namespec(namespec): # -> tuple[Unknown, Unknown | None]:
+    ...
+
+class ProcessException(Exception):
+    """ Specialized exceptions used when attempting to start a process """
+    ...
+
+
+class BadCommand(ProcessException):
+    """ Indicates the command could not be parsed properly. """
+    ...
+
+
+class NotExecutable(ProcessException):
+    """ Indicates that the filespec cannot be executed because its path
+    resolves to a file which is not executable, or which is a directory. """
+    ...
+
+
+class NotFound(ProcessException):
+    """ Indicates that the filespec cannot be executed because it could not
+    be found """
+    ...
+
+
+class NoPermission(ProcessException):
+    """ Indicates that the file cannot be executed because the supervisor
+    process does not possess the appropriate UNIX filesystem permission
+    to execute the file. """
+    ...
diff --git a/manager/typings/supervisor/pidproxy.pyi b/manager/typings/supervisor/pidproxy.pyi
new file mode 100644 (file)
index 0000000..21a4d3f
--- /dev/null
@@ -0,0 +1,33 @@
+"""
+This type stub file was generated by pyright.
+"""
+
+""" An executable which proxies for a subprocess; upon a signal, it sends that
+signal to the process identified by a pidfile. """
+class PidProxy:
+    pid = ...
+    def __init__(self, args) -> None:
+        ...
+    
+    def go(self): # -> None:
+        ...
+    
+    def usage(self): # -> None:
+        ...
+    
+    def setsignals(self): # -> None:
+        ...
+    
+    def reap(self, sig, frame): # -> None:
+        ...
+    
+    def passtochild(self, sig, frame): # -> None:
+        ...
+    
+
+
+def main(): # -> None:
+    ...
+
+if __name__ == '__main__':
+    ...
diff --git a/manager/typings/supervisor/poller.pyi b/manager/typings/supervisor/poller.pyi
new file mode 100644 (file)
index 0000000..f4b0d99
--- /dev/null
@@ -0,0 +1,127 @@
+"""
+This type stub file was generated by pyright.
+"""
+
+class BasePoller:
+    def __init__(self, options) -> None:
+        ...
+    
+    def initialize(self): # -> None:
+        ...
+    
+    def register_readable(self, fd):
+        ...
+    
+    def register_writable(self, fd):
+        ...
+    
+    def unregister_readable(self, fd):
+        ...
+    
+    def unregister_writable(self, fd):
+        ...
+    
+    def poll(self, timeout):
+        ...
+    
+    def before_daemonize(self): # -> None:
+        ...
+    
+    def after_daemonize(self): # -> None:
+        ...
+    
+    def close(self): # -> None:
+        ...
+    
+
+
+class SelectPoller(BasePoller):
+    def initialize(self): # -> None:
+        ...
+    
+    def register_readable(self, fd): # -> None:
+        ...
+    
+    def register_writable(self, fd): # -> None:
+        ...
+    
+    def unregister_readable(self, fd): # -> None:
+        ...
+    
+    def unregister_writable(self, fd): # -> None:
+        ...
+    
+    def unregister_all(self): # -> None:
+        ...
+    
+    def poll(self, timeout): # -> tuple[list[Unknown], list[Unknown]] | tuple[List[Any], List[Any]]:
+        ...
+    
+
+
+class PollPoller(BasePoller):
+    def initialize(self): # -> None:
+        ...
+    
+    def register_readable(self, fd): # -> None:
+        ...
+    
+    def register_writable(self, fd): # -> None:
+        ...
+    
+    def unregister_readable(self, fd): # -> None:
+        ...
+    
+    def unregister_writable(self, fd): # -> None:
+        ...
+    
+    def poll(self, timeout): # -> tuple[list[Unknown], list[Unknown]]:
+        ...
+    
+
+
+class KQueuePoller(BasePoller):
+    '''
+    Wrapper for select.kqueue()/kevent()
+    '''
+    max_events = ...
+    def initialize(self): # -> None:
+        ...
+    
+    def register_readable(self, fd): # -> None:
+        ...
+    
+    def register_writable(self, fd): # -> None:
+        ...
+    
+    def unregister_readable(self, fd): # -> None:
+        ...
+    
+    def unregister_writable(self, fd): # -> None:
+        ...
+    
+    def poll(self, timeout): # -> tuple[list[Unknown], list[Unknown]]:
+        ...
+    
+    def before_daemonize(self): # -> None:
+        ...
+    
+    def after_daemonize(self): # -> None:
+        ...
+    
+    def close(self): # -> None:
+        ...
+    
+
+
+def implements_poll(): # -> bool:
+    ...
+
+def implements_kqueue(): # -> bool:
+    ...
+
+if implements_kqueue():
+    Poller = ...
+else:
+    Poller = ...
+    Poller = ...
diff --git a/manager/typings/supervisor/process.pyi b/manager/typings/supervisor/process.pyi
new file mode 100644 (file)
index 0000000..df784aa
--- /dev/null
@@ -0,0 +1,224 @@
+"""
+This type stub file was generated by pyright.
+"""
+
+import functools
+
+@functools.total_ordering
+class Subprocess:
+    """A class to manage a subprocess."""
+    pid = ...
+    config = ...
+    state = ...
+    listener_state = ...
+    event = ...
+    laststart = ...
+    laststop = ...
+    laststopreport = ...
+    delay = ...
+    administrative_stop = ...
+    system_stop = ...
+    killing = ...
+    backoff = ...
+    dispatchers = ...
+    pipes = ...
+    exitstatus = ...
+    spawnerr = ...
+    group = ...
+    def __init__(self, config) -> None:
+        """Constructor.
+
+        Argument is a ProcessConfig instance.
+        """
+        ...
+    
+    def removelogs(self): # -> None:
+        ...
+    
+    def reopenlogs(self): # -> None:
+        ...
+    
+    def drain(self): # -> None:
+        ...
+    
+    def write(self, chars): # -> None:
+        ...
+    
+    def get_execv_args(self): # -> tuple[str | None, List[str]]:
+        """Internal: turn a program name into a file name, using $PATH,
+        make sure it exists / is executable, raising a ProcessException
+        if not """
+        ...
+    
+    event_map = ...
+    def change_state(self, new_state, expected=...): # -> Literal[False] | None:
+        ...
+    
+    def record_spawnerr(self, msg): # -> None:
+        ...
+    
+    def spawn(self): # -> None:
+        """Start the subprocess.  It must not be running already.
+
+        Return the process id.  If the fork() call fails, return None.
+        """
+        ...
+    
+    def stop(self): # -> str | None:
+        """ Administrative stop """
+        ...
+    
+    def stop_report(self): # -> None:
+        """ Log a 'waiting for x to stop' message with throttling. """
+        ...
+    
+    def give_up(self): # -> None:
+        ...
+    
+    def kill(self, sig): # -> str | None:
+        """Send a signal to the subprocess with the intention to kill
+        it (to make it exit).  This may or may not actually kill it.
+
+        Return None if the signal was sent, or an error message string
+        if an error occurred or if the subprocess is not running.
+        """
+        ...
+    
+    def signal(self, sig): # -> str | None:
+        """Send a signal to the subprocess, without intending to kill it.
+
+        Return None if the signal was sent, or an error message string
+        if an error occurred or if the subprocess is not running.
+        """
+        ...
+    
+    def finish(self, pid, sts): # -> None:
+        """ The process was reaped and we need to report and manage its state
+        """
+        ...
+    
+    def set_uid(self): # -> None:
+        ...
+    
+    def __lt__(self, other) -> bool:
+        ...
+    
+    def __eq__(self, other) -> bool:
+        ...
+    
+    def __repr__(self): # -> str:
+        ...
+    
+    def get_state(self): # -> int | None:
+        ...
+    
+    def transition(self): # -> None:
+        ...
+    
+
+
+class FastCGISubprocess(Subprocess):
+    """Extends Subprocess class to handle FastCGI subprocesses"""
+    def __init__(self, config) -> None:
+        ...
+    
+    def before_spawn(self): # -> None:
+        """
+        The FastCGI socket needs to be created by the parent before we fork
+        """
+        ...
+    
+    def spawn(self): # -> None:
+        """
+        Overrides Subprocess.spawn() so we can hook in before it happens
+        """
+        ...
+    
+    def after_finish(self): # -> None:
+        """
+        Releases reference to FastCGI socket when process is reaped
+        """
+        ...
+    
+    def finish(self, pid, sts): # -> None:
+        """
+        Overrides Subprocess.finish() so we can hook in after it happens
+        """
+        ...
+    
+
+
+@functools.total_ordering
+class ProcessGroupBase:
+    def __init__(self, config) -> None:
+        ...
+    
+    def __lt__(self, other) -> bool:
+        ...
+    
+    def __eq__(self, other) -> bool:
+        ...
+    
+    def __repr__(self): # -> str:
+        ...
+    
+    def removelogs(self): # -> None:
+        ...
+    
+    def reopenlogs(self): # -> None:
+        ...
+    
+    def stop_all(self): # -> None:
+        ...
+    
+    def get_unstopped_processes(self): # -> list[Unknown]:
+        """ Processes which aren't in a state that is considered 'stopped' """
+        ...
+    
+    def get_dispatchers(self): # -> dict[Unknown, Unknown]:
+        ...
+    
+    def before_remove(self): # -> None:
+        ...
+    
+
+
+class ProcessGroup(ProcessGroupBase):
+    def transition(self): # -> None:
+        ...
+    
+
+
+class FastCGIProcessGroup(ProcessGroup):
+    def __init__(self, config, **kwargs) -> None:
+        ...
+    
+
+
+class EventListenerPool(ProcessGroupBase):
+    def __init__(self, config) -> None:
+        ...
+    
+    def handle_rejected(self, event): # -> None:
+        ...
+    
+    def transition(self): # -> None:
+        ...
+    
+    def before_remove(self): # -> None:
+        ...
+    
+    def dispatch(self): # -> None:
+        ...
+    
+
+
+class GlobalSerial:
+    def __init__(self) -> None:
+        ...
+    
+
+
+GlobalSerial = ...
+def new_serial(inst):
+    ...
diff --git a/manager/typings/supervisor/rpcinterface.pyi b/manager/typings/supervisor/rpcinterface.pyi
new file mode 100644 (file)
index 0000000..df40334
--- /dev/null
@@ -0,0 +1,336 @@
+"""
+This type stub file was generated by pyright.
+"""
+
+API_VERSION = ...
+class SupervisorNamespaceRPCInterface:
+    def __init__(self, supervisord) -> None:
+        ...
+    
+    def getAPIVersion(self): # -> Literal['3.0']:
+        """ Return the version of the RPC API used by supervisord
+
+        @return string version version id
+        """
+        ...
+    
+    getVersion = ...
+    def getSupervisorVersion(self): # -> str:
+        """ Return the version of the supervisor package in use by supervisord
+
+        @return string version version id
+        """
+        ...
+    
+    def getIdentification(self):
+        """ Return identifying string of supervisord
+
+        @return string identifier identifying string
+        """
+        ...
+    
+    def getState(self): # -> dict[str, Unknown | None]:
+        """ Return current state of supervisord as a struct
+
+        @return struct A struct with keys int statecode, string statename
+        """
+        ...
+    
+    def getPID(self):
+        """ Return the PID of supervisord
+
+        @return int PID
+        """
+        ...
+    
+    def readLog(self, offset, length): # -> str:
+        """ Read length bytes from the main log starting at offset
+
+        @param int offset         offset to start reading from.
+        @param int length         number of bytes to read from the log.
+        @return string result     Bytes of log
+        """
+        ...
+    
+    readMainLog = ...
+    def clearLog(self): # -> Literal[True]:
+        """ Clear the main log.
+
+        @return boolean result always returns True unless error
+        """
+        ...
+    
+    def shutdown(self): # -> Literal[True]:
+        """ Shut down the supervisor process
+
+        @return boolean result always returns True unless error
+        """
+        ...
+    
+    def restart(self): # -> Literal[True]:
+        """ Restart the supervisor process
+
+        @return boolean result  always return True unless error
+        """
+        ...
+    
+    def reloadConfig(self): # -> list[list[list[Unknown]]]:
+        """
+        Reload the configuration.
+
+        The result contains three arrays containing names of process
+        groups:
+
+        * `added` gives the process groups that have been added
+        * `changed` gives the process groups whose contents have
+          changed
+        * `removed` gives the process groups that are no longer
+          in the configuration
+
+        @return array result  [[added, changed, removed]]
+
+        """
+        ...
+    
+    def addProcessGroup(self, name): # -> Literal[True]:
+        """ Update the config for a running process from config file.
+
+        @param string name         name of process group to add
+        @return boolean result     true if successful
+        """
+        ...
+    
+    def removeProcessGroup(self, name): # -> Literal[True]:
+        """ Remove a stopped process from the active configuration.
+
+        @param string name         name of process group to remove
+        @return boolean result     Indicates whether the removal was successful
+        """
+        ...
+    
+    def startProcess(self, name, wait=...): # -> () -> (Type[NOT_DONE_YET] | Literal[True]) | Literal[True]:
+        """ Start a process
+
+        @param string name Process name (or ``group:name``, or ``group:*``)
+        @param boolean wait Wait for process to be fully started
+        @return boolean result     Always true unless error
+
+        """
+        ...
+    
+    def startProcessGroup(self, name, wait=...): # -> (processes: Unknown = processes, predicate: Unknown = predicate, func: Unknown = func, extra_kwargs: Unknown = extra_kwargs, callbacks: Unknown = callbacks, results: Unknown = results) -> (Unknown | Type[NOT_DONE_YET]):
+        """ Start all processes in the group named 'name'
+
+        @param string name     The group name
+        @param boolean wait    Wait for each process to be fully started
+        @return array result   An array of process status info structs
+        """
+        ...
+    
+    def startAllProcesses(self, wait=...): # -> (processes: Unknown = processes, predicate: Unknown = predicate, func: Unknown = func, extra_kwargs: Unknown = extra_kwargs, callbacks: Unknown = callbacks, results: Unknown = results) -> (Unknown | Type[NOT_DONE_YET]):
+        """ Start all processes listed in the configuration file
+
+        @param boolean wait    Wait for each process to be fully started
+        @return array result   An array of process status info structs
+        """
+        ...
+    
+    def stopProcess(self, name, wait=...): # -> () -> (Type[NOT_DONE_YET] | Literal[True]) | Literal[True]:
+        """ Stop a process named by name
+
+        @param string name  The name of the process to stop (or 'group:name')
+        @param boolean wait        Wait for the process to be fully stopped
+        @return boolean result     Always return True unless error
+        """
+        ...
+    
+    def stopProcessGroup(self, name, wait=...): # -> (processes: Unknown = processes, predicate: Unknown = predicate, func: Unknown = func, extra_kwargs: Unknown = extra_kwargs, callbacks: Unknown = callbacks, results: Unknown = results) -> (Unknown | Type[NOT_DONE_YET]):
+        """ Stop all processes in the process group named 'name'
+
+        @param string name     The group name
+        @param boolean wait    Wait for each process to be fully stopped
+        @return array result   An array of process status info structs
+        """
+        ...
+    
+    def stopAllProcesses(self, wait=...): # -> (processes: Unknown = processes, predicate: Unknown = predicate, func: Unknown = func, extra_kwargs: Unknown = extra_kwargs, callbacks: Unknown = callbacks, results: Unknown = results) -> (Unknown | Type[NOT_DONE_YET]):
+        """ Stop all processes in the process list
+
+        @param  boolean wait   Wait for each process to be fully stopped
+        @return array result   An array of process status info structs
+        """
+        ...
+    
+    def signalProcess(self, name, signal): # -> Literal[True]:
+        """ Send an arbitrary UNIX signal to the process named by name
+
+        @param string name    Name of the process to signal (or 'group:name')
+        @param string signal  Signal to send, as name ('HUP') or number ('1')
+        @return boolean
+        """
+        ...
+    
+    def signalProcessGroup(self, name, signal): # -> Type[NOT_DONE_YET] | list[Unknown]:
+        """ Send a signal to all processes in the group named 'name'
+
+        @param string name    The group name
+        @param string signal  Signal to send, as name ('HUP') or number ('1')
+        @return array
+        """
+        ...
+    
+    def signalAllProcesses(self, signal): # -> Type[NOT_DONE_YET] | list[Unknown]:
+        """ Send a signal to all processes in the process list
+
+        @param string signal  Signal to send, as name ('HUP') or number ('1')
+        @return array         An array of process status info structs
+        """
+        ...
+    
+    def getAllConfigInfo(self): # -> list[Unknown]:
+        """ Get info about all available process configurations. Each struct
+        represents a single process (i.e. groups get flattened).
+
+        @return array result  An array of process config info structs
+        """
+        ...
+    
+    def getProcessInfo(self, name): # -> dict[str, Unknown | int | str | None]:
+        """ Get info about a process named name
+
+        @param string name The name of the process (or 'group:name')
+        @return struct result     A structure containing data about the process
+        """
+        ...
+    
+    def getAllProcessInfo(self): # -> list[Unknown]:
+        """ Get info about all processes
+
+        @return array result  An array of process status results
+        """
+        ...
+    
+    def readProcessStdoutLog(self, name, offset, length): # -> str:
+        """ Read length bytes from name's stdout log starting at offset
+
+        @param string name        the name of the process (or 'group:name')
+        @param int offset         offset to start reading from.
+        @param int length         number of bytes to read from the log.
+        @return string result     Bytes of log
+        """
+        ...
+    
+    readProcessLog = ...
+    def readProcessStderrLog(self, name, offset, length): # -> str:
+        """ Read length bytes from name's stderr log starting at offset
+
+        @param string name        the name of the process (or 'group:name')
+        @param int offset         offset to start reading from.
+        @param int length         number of bytes to read from the log.
+        @return string result     Bytes of log
+        """
+        ...
+    
+    def tailProcessStdoutLog(self, name, offset, length): # -> list[str | int | bool]:
+        """
+        Provides a more efficient way to tail the (stdout) log than
+        readProcessStdoutLog().  Use readProcessStdoutLog() to read
+        chunks and tailProcessStdoutLog() to tail.
+
+        Requests (length) bytes from the (name)'s log, starting at
+        (offset).  If the total log size is greater than (offset +
+        length), the overflow flag is set and the (offset) is
+        automatically increased to position the buffer at the end of
+        the log.  If less than (length) bytes are available, the
+        maximum number of available bytes will be returned.  (offset)
+        returned is always the last offset in the log +1.
+
+        @param string name         the name of the process (or 'group:name')
+        @param int offset          offset to start reading from
+        @param int length          maximum number of bytes to return
+        @return array result       [string bytes, int offset, bool overflow]
+        """
+        ...
+    
+    tailProcessLog = ...
+    def tailProcessStderrLog(self, name, offset, length): # -> list[str | int | bool]:
+        """
+        Provides a more efficient way to tail the (stderr) log than
+        readProcessStderrLog().  Use readProcessStderrLog() to read
+        chunks and tailProcessStderrLog() to tail.
+
+        Requests (length) bytes from the (name)'s log, starting at
+        (offset).  If the total log size is greater than (offset +
+        length), the overflow flag is set and the (offset) is
+        automatically increased to position the buffer at the end of
+        the log.  If less than (length) bytes are available, the
+        maximum number of available bytes will be returned.  (offset)
+        returned is always the last offset in the log +1.
+
+        @param string name         the name of the process (or 'group:name')
+        @param int offset          offset to start reading from
+        @param int length          maximum number of bytes to return
+        @return array result       [string bytes, int offset, bool overflow]
+        """
+        ...
+    
+    def clearProcessLogs(self, name): # -> Literal[True]:
+        """ Clear the stdout and stderr logs for the named process and
+        reopen them.
+
+        @param string name   The name of the process (or 'group:name')
+        @return boolean result      Always True unless error
+        """
+        ...
+    
+    clearProcessLog = ...
+    def clearAllProcessLogs(self): # -> () -> (Type[NOT_DONE_YET] | list[Unknown]):
+        """ Clear all process log files
+
+        @return array result   An array of process status info structs
+        """
+        ...
+    
+    def sendProcessStdin(self, name, chars): # -> Literal[True]:
+        """ Send a string of chars to the stdin of the process name.
+        If non-7-bit data is sent (unicode), it is encoded to utf-8
+        before being sent to the process' stdin.  If chars is not a
+        string or is not unicode, raise INCORRECT_PARAMETERS.  If the
+        process is not running, raise NOT_RUNNING.  If the process'
+        stdin cannot accept input (e.g. it was closed by the child
+        process), raise NO_FILE.
+
+        @param string name        The process name to send to (or 'group:name')
+        @param string chars       The character data to send to the process
+        @return boolean result    Always return True unless error
+        """
+        ...
+    
+    def sendRemoteCommEvent(self, type, data): # -> Literal[True]:
+        """ Send an event that will be received by event listener
+        subprocesses subscribing to the RemoteCommunicationEvent.
+
+        @param  string  type  String for the "type" key in the event header
+        @param  string  data  Data for the event body
+        @return boolean       Always return True unless error
+        """
+        ...
+    
+
+
+def make_allfunc(processes, predicate, func, **extra_kwargs): # -> (processes: Unknown = processes, predicate: Unknown = predicate, func: Unknown = func, extra_kwargs: Unknown = extra_kwargs, callbacks: Unknown = callbacks, results: Unknown = results) -> (Unknown | Type[NOT_DONE_YET]):
+    """ Return a closure representing a function that calls a
+    function for every process, and returns a result """
+    ...
+
+def isRunning(process): # -> bool:
+    ...
+
+def isNotRunning(process): # -> bool:
+    ...
+
+def isSignallable(process): # -> Literal[True] | None:
+    ...
+
+def make_main_rpcinterface(supervisord): # -> SupervisorNamespaceRPCInterface:
+    ...
diff --git a/manager/typings/supervisor/socket_manager.pyi b/manager/typings/supervisor/socket_manager.pyi
new file mode 100644 (file)
index 0000000..05b26f0
--- /dev/null
@@ -0,0 +1,59 @@
+"""
+This type stub file was generated by pyright.
+"""
+
+class Proxy:
+    """ Class for wrapping a shared resource object and getting
+        notified when it's deleted
+    """
+    def __init__(self, object, **kwargs) -> None:
+        ...
+    
+    def __del__(self): # -> None:
+        ...
+    
+    def __getattr__(self, name): # -> Any:
+        ...
+    
+
+
+class ReferenceCounter:
+    """ Class for tracking references to a shared resource
+    """
+    def __init__(self, **kwargs) -> None:
+        ...
+    
+    def get_count(self): # -> int:
+        ...
+    
+    def increment(self): # -> None:
+        ...
+    
+    def decrement(self): # -> None:
+        ...
+    
+
+
+class SocketManager:
+    """ Class for managing sockets in servers that create/bind/listen
+        before forking multiple child processes to accept()
+        Sockets are managed at the process group level and referenced counted
+        at the process level b/c that's really the only place to hook in
+    """
+    def __init__(self, socket_config, **kwargs) -> None:
+        ...
+    
+    def __repr__(self): # -> str:
+        ...
+    
+    def config(self): # -> Unknown:
+        ...
+    
+    def is_prepared(self): # -> bool:
+        ...
+    
+    def get_socket(self): # -> Proxy:
+        ...
+    
+    def get_socket_ref_count(self): # -> int:
+        ...
diff --git a/manager/typings/supervisor/states.pyi b/manager/typings/supervisor/states.pyi
new file mode 100644 (file)
index 0000000..d0a21f5
--- /dev/null
@@ -0,0 +1,44 @@
+"""
+This type stub file was generated by pyright.
+"""
+
+class ProcessStates:
+    STOPPED = ...
+    STARTING = ...
+    RUNNING = ...
+    BACKOFF = ...
+    STOPPING = ...
+    EXITED = ...
+    FATAL = ...
+    UNKNOWN = ...
+
+
+STOPPED_STATES = ...
+RUNNING_STATES = ...
+SIGNALLABLE_STATES = ...
+def getProcessStateDescription(code): # -> None:
+    ...
+
+class SupervisorStates:
+    FATAL = ...
+    RUNNING = ...
+    RESTARTING = ...
+    SHUTDOWN = ...
+
+
+def getSupervisorStateDescription(code): # -> None:
+    ...
+
+class EventListenerStates:
+    READY = ...
+    BUSY = ...
+    ACKNOWLEDGED = ...
+    UNKNOWN = ...
+
+
+def getEventListenerStateDescription(code): # -> None:
+    ...
+
+_process_states_by_code = ...
+_supervisor_states_by_code = ...
+_eventlistener_states_by_code = ...
diff --git a/manager/typings/supervisor/supervisorctl.pyi b/manager/typings/supervisor/supervisorctl.pyi
new file mode 100644 (file)
index 0000000..3034638
--- /dev/null
@@ -0,0 +1,280 @@
+"""
+This type stub file was generated by pyright.
+"""
+
+import cmd
+import threading
+
+"""supervisorctl -- control applications run by supervisord from the cmd line.
+
+Usage: %s [options] [action [arguments]]
+
+Options:
+-c/--configuration FILENAME -- configuration file path (searches if not given)
+-h/--help -- print usage message and exit
+-i/--interactive -- start an interactive shell after executing commands
+-s/--serverurl URL -- URL on which supervisord server is listening
+     (default "http://localhost:9001").
+-u/--username USERNAME -- username to use for authentication with server
+-p/--password PASSWORD -- password to use for authentication with server
+-r/--history-file -- keep a readline history (if readline is available)
+
+action [arguments] -- see below
+
+Actions are commands like "tail" or "stop".  If -i is specified or no action is
+specified on the command line, a "shell" interpreting actions typed
+interactively is started.  Use the action "help" to find out about available
+actions.
+"""
+class LSBInitExitStatuses:
+    SUCCESS = ...
+    GENERIC = ...
+    INVALID_ARGS = ...
+    UNIMPLEMENTED_FEATURE = ...
+    INSUFFICIENT_PRIVILEGES = ...
+    NOT_INSTALLED = ...
+    NOT_RUNNING = ...
+
+
+class LSBStatusExitStatuses:
+    NOT_RUNNING = ...
+    UNKNOWN = ...
+
+
+DEAD_PROGRAM_FAULTS = ...
+class fgthread(threading.Thread):
+    """ A subclass of threading.Thread, with a kill() method.
+    To be used for foreground output/error streaming.
+    http://mail.python.org/pipermail/python-list/2004-May/260937.html
+    """
+    def __init__(self, program, ctl) -> None:
+        ...
+    
+    def start(self): # -> None:
+        ...
+    
+    def run(self): # -> None:
+        ...
+    
+    def globaltrace(self, frame, why, arg): # -> (frame: Unknown, why: Unknown, arg: Unknown) -> (frame: Unknown, why: Unknown, arg: Unknown) -> Unknown | None:
+        ...
+    
+    def localtrace(self, frame, why, arg): # -> (frame: Unknown, why: Unknown, arg: Unknown) -> Unknown:
+        ...
+    
+    def kill(self): # -> None:
+        ...
+    
+
+
+class Controller(cmd.Cmd):
+    def __init__(self, options, completekey=..., stdin=..., stdout=...) -> None:
+        ...
+    
+    def emptyline(self): # -> None:
+        ...
+    
+    def default(self, line): # -> None:
+        ...
+    
+    def exec_cmdloop(self, args, options): # -> None:
+        ...
+    
+    def set_exitstatus_from_xmlrpc_fault(self, faultcode, ignored_faultcode=...): # -> None:
+        ...
+    
+    def onecmd(self, line): # -> Any | None:
+        """ Override the onecmd method to:
+          - catch and print all exceptions
+          - call 'do_foo' on plugins rather than ourself
+        """
+        ...
+    
+    def output(self, message): # -> None:
+        ...
+    
+    def get_supervisor(self): # -> Any:
+        ...
+    
+    def get_server_proxy(self, namespace=...): # -> Any:
+        ...
+    
+    def upcheck(self): # -> bool:
+        ...
+    
+    def complete(self, text, state, line=...): # -> str | None:
+        """Completer function that Cmd will register with readline using
+        readline.set_completer().  This function will be called by readline
+        as complete(text, state) where text is a fragment to complete and
+        state is an integer (0..n).  Each call returns a string with a new
+        completion.  When no more are available, None is returned."""
+        ...
+    
+    def do_help(self, arg): # -> None:
+        ...
+    
+    def help_help(self): # -> None:
+        ...
+    
+    def do_EOF(self, arg): # -> Literal[1]:
+        ...
+    
+    def help_EOF(self): # -> None:
+        ...
+    
+
+
+def get_names(inst): # -> List[Unknown]:
+    ...
+
+class ControllerPluginBase:
+    name = ...
+    def __init__(self, controller) -> None:
+        ...
+    
+    doc_header = ...
+    def do_help(self, arg): # -> None:
+        ...
+    
+
+
+def not_all_langs(): # -> str | None:
+    ...
+
+def check_encoding(ctl): # -> None:
+    ...
+
+class DefaultControllerPlugin(ControllerPluginBase):
+    name = ...
+    listener = ...
+    def do_tail(self, arg): # -> None:
+        ...
+    
+    def help_tail(self): # -> None:
+        ...
+    
+    def do_maintail(self, arg): # -> None:
+        ...
+    
+    def help_maintail(self): # -> None:
+        ...
+    
+    def do_quit(self, arg):
+        ...
+    
+    def help_quit(self): # -> None:
+        ...
+    
+    do_exit = ...
+    def help_exit(self): # -> None:
+        ...
+    
+    def do_status(self, arg): # -> None:
+        ...
+    
+    def help_status(self): # -> None:
+        ...
+    
+    def do_pid(self, arg): # -> None:
+        ...
+    
+    def help_pid(self): # -> None:
+        ...
+    
+    def do_start(self, arg): # -> None:
+        ...
+    
+    def help_start(self): # -> None:
+        ...
+    
+    def do_stop(self, arg): # -> None:
+        ...
+    
+    def help_stop(self): # -> None:
+        ...
+    
+    def do_signal(self, arg): # -> None:
+        ...
+    
+    def help_signal(self): # -> None:
+        ...
+    
+    def do_restart(self, arg): # -> None:
+        ...
+    
+    def help_restart(self): # -> None:
+        ...
+    
+    def do_shutdown(self, arg): # -> None:
+        ...
+    
+    def help_shutdown(self): # -> None:
+        ...
+    
+    def do_reload(self, arg): # -> None:
+        ...
+    
+    def help_reload(self): # -> None:
+        ...
+    
+    def do_avail(self, arg): # -> None:
+        ...
+    
+    def help_avail(self): # -> None:
+        ...
+    
+    def do_reread(self, arg): # -> None:
+        ...
+    
+    def help_reread(self): # -> None:
+        ...
+    
+    def do_add(self, arg): # -> None:
+        ...
+    
+    def help_add(self): # -> None:
+        ...
+    
+    def do_remove(self, arg): # -> None:
+        ...
+    
+    def help_remove(self): # -> None:
+        ...
+    
+    def do_update(self, arg): # -> None:
+        ...
+    
+    def help_update(self): # -> None:
+        ...
+    
+    def do_clear(self, arg): # -> None:
+        ...
+    
+    def help_clear(self): # -> None:
+        ...
+    
+    def do_open(self, arg): # -> None:
+        ...
+    
+    def help_open(self): # -> None:
+        ...
+    
+    def do_version(self, arg): # -> None:
+        ...
+    
+    def help_version(self): # -> None:
+        ...
+    
+    def do_fg(self, arg): # -> None:
+        ...
+    
+    def help_fg(self, args=...): # -> None:
+        ...
+    
+
+
+def main(args=..., options=...): # -> None:
+    ...
+
+if __name__ == "__main__":
+    ...
diff --git a/manager/typings/supervisor/supervisord.pyi b/manager/typings/supervisor/supervisord.pyi
new file mode 100644 (file)
index 0000000..5990f96
--- /dev/null
@@ -0,0 +1,102 @@
+"""
+This type stub file was generated by pyright.
+"""
+
+"""supervisord -- run a set of applications as daemons.
+
+Usage: %s [options]
+
+Options:
+-c/--configuration FILENAME -- configuration file path (searches if not given)
+-n/--nodaemon -- run in the foreground (same as 'nodaemon=true' in config file)
+-s/--silent -- no logs to stdout (maps to 'silent=true' in config file)
+-h/--help -- print this usage message and exit
+-v/--version -- print supervisord version number and exit
+-u/--user USER -- run supervisord as this user (or numeric uid)
+-m/--umask UMASK -- use this umask for daemon subprocess (default is 022)
+-d/--directory DIRECTORY -- directory to chdir to when daemonized
+-l/--logfile FILENAME -- use FILENAME as logfile path
+-y/--logfile_maxbytes BYTES -- use BYTES to limit the max size of logfile
+-z/--logfile_backups NUM -- number of backups to keep when max bytes reached
+-e/--loglevel LEVEL -- use LEVEL as log level (debug,info,warn,error,critical)
+-j/--pidfile FILENAME -- write a pid file for the daemon process to FILENAME
+-i/--identifier STR -- identifier used for this instance of supervisord
+-q/--childlogdir DIRECTORY -- the log directory for child process logs
+-k/--nocleanup --  prevent the process from performing cleanup (removal of
+                   old automatic child log files) at startup.
+-a/--minfds NUM -- the minimum number of file descriptors for start success
+-t/--strip_ansi -- strip ansi escape codes from process output
+--minprocs NUM  -- the minimum number of processes available for start success
+--profile_options OPTIONS -- run supervisord under profiler and output
+                             results based on OPTIONS, which  is a comma-sep'd
+                             list of 'cumulative', 'calls', and/or 'callers',
+                             e.g. 'cumulative,callers')
+"""
+class Supervisor:
+    stopping = ...
+    lastshutdownreport = ...
+    process_groups = ...
+    stop_groups = ...
+    def __init__(self, options) -> None:
+        ...
+    
+    def main(self): # -> None:
+        ...
+    
+    def run(self): # -> None:
+        ...
+    
+    def diff_to_active(self): # -> tuple[list[Unknown], list[Unknown], list[Unknown]]:
+        ...
+    
+    def add_process_group(self, config): # -> bool:
+        ...
+    
+    def remove_process_group(self, name): # -> bool:
+        ...
+    
+    def get_process_map(self): # -> dict[Unknown, Unknown]:
+        ...
+    
+    def shutdown_report(self): # -> list[Unknown]:
+        ...
+    
+    def ordered_stop_groups_phase_1(self): # -> None:
+        ...
+    
+    def ordered_stop_groups_phase_2(self): # -> None:
+        ...
+    
+    def runforever(self): # -> None:
+        ...
+    
+    def tick(self, now=...): # -> None:
+        """ Send one or more 'tick' events when the timeslice related to
+        the period for the event type rolls over """
+        ...
+    
+    def reap(self, once=..., recursionguard=...): # -> None:
+        ...
+    
+    def handle_signal(self): # -> None:
+        ...
+    
+    def get_state(self):
+        ...
+    
+
+
+def timeslice(period, when): # -> int:
+    ...
+
+def profile(cmd, globals, locals, sort_order, callers): # -> None:
+    ...
+
+def main(args=..., test=...): # -> None:
+    ...
+
+def go(options): # -> None:
+    ...
+
+if __name__ == "__main__":
+    ...
diff --git a/manager/typings/supervisor/templating.pyi b/manager/typings/supervisor/templating.pyi
new file mode 100644 (file)
index 0000000..e122826
--- /dev/null
@@ -0,0 +1,476 @@
+"""
+This type stub file was generated by pyright.
+"""
+
+from xml.etree.ElementTree import TreeBuilder
+
+from supervisor.compat import PY2, HTMLParser
+
+AUTOCLOSE = ...
+IGNOREEND = ...
+_BLANK = ...
+_SPACE = ...
+_EQUAL = ...
+_QUOTE = ...
+_OPEN_TAG_START = ...
+_CLOSE_TAG_START = ...
+_OPEN_TAG_END = ...
+_SELF_CLOSE = ...
+_OMITTED_TEXT = ...
+_COMMENT_START = ...
+_COMMENT_END = ...
+_PI_START = ...
+_PI_END = ...
+_AMPER_ESCAPED = ...
+_LT = ...
+_LT_ESCAPED = ...
+_QUOTE_ESCAPED = ...
+_XML_PROLOG_BEGIN = ...
+_ENCODING = ...
+_XML_PROLOG_END = ...
+_DOCTYPE_BEGIN = ...
+_PUBLIC = ...
+_DOCTYPE_END = ...
+if PY2:
+    def encode(text, encoding):
+        ...
+    
+else:
+    def encode(text, encoding): # -> bytes:
+        ...
+    
+def Replace(text, structure=...): # -> _MeldElementInterface:
+    ...
+
+class PyHelper:
+    def findmeld(self, node, name, default=...):
+        ...
+    
+    def clone(self, node, parent=...): # -> _MeldElementInterface:
+        ...
+    
+    def bfclone(self, node, parent=...): # -> _MeldElementInterface:
+        ...
+    
+    def getiterator(self, node, tag=...): # -> list[Unknown]:
+        ...
+    
+    def content(self, node, text, structure=...): # -> None:
+        ...
+    
+
+
+helper = ...
+_MELD_NS_URL = ...
+_MELD_PREFIX = ...
+_MELD_LOCAL = ...
+_MELD_ID = ...
+_MELD_SHORT_ID = ...
+_XHTML_NS_URL = ...
+_XHTML_PREFIX = ...
+_XHTML_PREFIX_LEN = ...
+_marker = ...
+class doctype:
+    html_strict = ...
+    html = ...
+    xhtml_strict = ...
+    xhtml = ...
+
+
+class _MeldElementInterface:
+    parent = ...
+    attrib = ...
+    text = ...
+    tail = ...
+    structure = ...
+    def __init__(self, tag, attrib) -> None:
+        ...
+    
+    def __repr__(self): # -> str:
+        ...
+    
+    def __len__(self): # -> int:
+        ...
+    
+    def __getitem__(self, index):
+        ...
+    
+    def __getslice__(self, start, stop): # -> List[Unknown]:
+        ...
+    
+    def getchildren(self): # -> list[Unknown]:
+        ...
+    
+    def find(self, path):
+        ...
+    
+    def findtext(self, path, default=...):
+        ...
+    
+    def findall(self, path):
+        ...
+    
+    def clear(self): # -> None:
+        ...
+    
+    def get(self, key, default=...):
+        ...
+    
+    def set(self, key, value): # -> None:
+        ...
+    
+    def keys(self): # -> list[Unknown]:
+        ...
+    
+    def items(self): # -> list[Unknown]:
+        ...
+    
+    def getiterator(self, *ignored_args, **ignored_kw): # -> list[Unknown]:
+        ...
+    
+    def __setitem__(self, index, element): # -> None:
+        ...
+    
+    def __setslice__(self, start, stop, elements): # -> None:
+        ...
+    
+    def append(self, element): # -> None:
+        ...
+    
+    def insert(self, index, element): # -> None:
+        ...
+    
+    def __delitem__(self, index): # -> None:
+        ...
+    
+    def __delslice__(self, start, stop): # -> None:
+        ...
+    
+    def remove(self, element): # -> None:
+        ...
+    
+    def makeelement(self, tag, attrib): # -> _MeldElementInterface:
+        ...
+    
+    def __mod__(self, other): # -> list[Unknown]:
+        """ Fill in the text values of meld nodes in tree; only
+        support dictionarylike operand (sequence operand doesn't seem
+        to make sense here)"""
+        ...
+    
+    def fillmelds(self, **kw): # -> list[Unknown]:
+        """ Fill in the text values of meld nodes in tree using the
+        keyword arguments passed in; use the keyword keys as meld ids
+        and the keyword values as text that should fill in the node
+        text on which that meld id is found.  Return a list of keys
+        from **kw that were not able to be found anywhere in the tree.
+        Never raises an exception. """
+        ...
+    
+    def fillmeldhtmlform(self, **kw): # -> list[Unknown]:
+        """ Perform magic to 'fill in' HTML form element values from a
+        dictionary.  Unlike 'fillmelds', the type of element being
+        'filled' is taken into consideration.
+
+        Perform a 'findmeld' on each key in the dictionary and use the
+        value that corresponds to the key to perform mutation of the
+        tree, changing data in what is presumed to be one or more HTML
+        form elements according to the following rules::
+
+          If the found element is an 'input group' (its meld id ends
+          with the string ':inputgroup'), set the 'checked' attribute
+          on the appropriate subelement which has a 'value' attribute
+          which matches the dictionary value.  Also remove the
+          'checked' attribute from every other 'input' subelement of
+          the input group.  If no input subelement's value matches the
+          dictionary value, this key is treated as 'unfilled'.
+
+          If the found element is an 'input type=text', 'input
+          type=hidden', 'input type=submit', 'input type=password',
+          'input type=reset' or 'input type=file' element, replace its
+          'value' attribute with the value.
+
+          If the found element is an 'input type=checkbox' or 'input
+          type='radio' element, set its 'checked' attribute to true if
+          the dict value is true, or remove its 'checked' attribute if
+          the dict value is false.
+
+          If the found element is a 'select' element and the value
+          exists in the 'value=' attribute of one of its 'option'
+          subelements, change that option's 'selected' attribute to
+          true and mark all other option elements as unselected.  If
+          the select element does not contain an option with a value
+          that matches the dictionary value, do nothing and return
+          this key as unfilled.
+
+          If the found element is a 'textarea' or any other kind of
+          element, replace its text with the value.
+
+          If the element corresponding to the key is not found,
+          do nothing and treat the key as 'unfilled'.
+
+        Return a list of 'unfilled' keys, representing meld ids
+        present in the dictionary but not present in the element tree
+        or meld ids which could not be filled due to the lack of any
+        matching subelements for 'select' nodes or 'inputgroup' nodes.
+        """
+        ...
+    
+    def findmeld(self, name, default=...):
+        """ Find a node in the tree that has a 'meld id' corresponding
+        to 'name'. Iterate over all subnodes recursively looking for a
+        node which matches.  If we can't find the node, return None."""
+        ...
+    
+    def findmelds(self): # -> list[Unknown]:
+        """ Find all nodes that have a meld id attribute and return
+        the found nodes in a list"""
+        ...
+    
+    def findwithattrib(self, attrib, value=...): # -> list[Unknown]:
+        """ Find all nodes that have an attribute named 'attrib'.  If
+        'value' is not None, omit nodes on which the attribute value
+        does not compare equally to 'value'. Return the found nodes in
+        a list."""
+        ...
+    
+    def repeat(self, iterable, childname=...): # -> list[Unknown]:
+        """repeats an element with values from an iterable.  If
+        'childname' is not None, repeat the element on which the
+        repeat is called, otherwise find the child element with a
+        'meld:id' matching 'childname' and repeat that.  The element
+        is repeated within its parent element (nodes that are created
+        as a result of a repeat share the same parent).  This method
+        returns an iterable; the value of each iteration is a
+        two-sequence in the form (newelement, data).  'newelement' is
+        a clone of the template element (including clones of its
+        children) which has already been seated in its parent element
+        in the template. 'data' is a value from the passed in
+        iterable.  Changing 'newelement' (typically based on values
+        from 'data') mutates the element 'in place'."""
+        ...
+    
+    def replace(self, text, structure=...): # -> None:
+        """ Replace this element with a Replace node in our parent with
+        the text 'text' and return the index of our position in
+        our parent.  If we have no parent, do nothing, and return None.
+        Pass the 'structure' flag to the replace node so it can do the right
+        thing at render time. """
+        ...
+    
+    def content(self, text, structure=...): # -> None:
+        """ Delete this node's children and append a Replace node that
+        contains text.  Always return None.  Pass the 'structure' flag
+        to the replace node so it can do the right thing at render
+        time."""
+        ...
+    
+    def attributes(self, **kw): # -> None:
+        """ Set attributes on this node. """
+        ...
+    
+    def write_xmlstring(self, encoding=..., doctype=..., fragment=..., declaration=..., pipeline=...): # -> bytes:
+        ...
+    
+    def write_xml(self, file, encoding=..., doctype=..., fragment=..., declaration=..., pipeline=...): # -> None:
+        """ Write XML to 'file' (which can be a filename or filelike object)
+
+        encoding    - encoding string (if None, 'utf-8' encoding is assumed)
+                      Must be a recognizable Python encoding type.
+        doctype     - 3-tuple indicating name, pubid, system of doctype.
+                      The default is to prevent a doctype from being emitted.
+        fragment    - True if a 'fragment' should be emitted for this node (no
+                      declaration, no doctype).  This causes both the
+                      'declaration' and 'doctype' parameters to become ignored
+                      if provided.
+        declaration - emit an xml declaration header (including an encoding
+                      if it's not None).  The default is to emit the
+                      doctype.
+        pipeline    - preserve 'meld' namespace identifiers in output
+                      for use in pipelining
+        """
+        ...
+    
+    def write_htmlstring(self, encoding=..., doctype=..., fragment=...): # -> bytes:
+        ...
+    
+    def write_html(self, file, encoding=..., doctype=..., fragment=...): # -> None:
+        """ Write HTML to 'file' (which can be a filename or filelike object)
+
+        encoding    - encoding string (if None, 'utf-8' encoding is assumed).
+                      Unlike XML output, this is not used in a declaration,
+                      but it is used to do actual character encoding during
+                      output.  Must be a recognizable Python encoding type.
+        doctype     - 3-tuple indicating name, pubid, system of doctype.
+                      The default is the value of doctype.html (HTML 4.0
+                      'loose')
+        fragment    - True if a "fragment" should be omitted (no doctype).
+                      This overrides any provided "doctype" parameter if
+                      provided.
+
+        Namespace'd elements and attributes have their namespaces removed
+        during output when writing HTML, so pipelining cannot be performed.
+
+        HTML is not valid XML, so an XML declaration header is never emitted.
+        """
+        ...
+    
+    def write_xhtmlstring(self, encoding=..., doctype=..., fragment=..., declaration=..., pipeline=...): # -> bytes:
+        ...
+    
+    def write_xhtml(self, file, encoding=..., doctype=..., fragment=..., declaration=..., pipeline=...): # -> None:
+        """ Write XHTML to 'file' (which can be a filename or filelike object)
+
+        encoding    - encoding string (if None, 'utf-8' encoding is assumed)
+                      Must be a recognizable Python encoding type.
+        doctype     - 3-tuple indicating name, pubid, system of doctype.
+                      The default is the value of doctype.xhtml (XHTML
+                      'loose').
+        fragment    - True if a 'fragment' should be emitted for this node (no
+                      declaration, no doctype).  This causes both the
+                      'declaration' and 'doctype' parameters to be ignored.
+        declaration - emit an xml declaration header (including an encoding
+                      string if 'encoding' is not None)
+        pipeline    - preserve 'meld' namespace identifiers in output
+                      for use in pipelining
+        """
+        ...
+    
+    def clone(self, parent=...): # -> _MeldElementInterface:
+        """ Create a clone of an element.  If parent is not None,
+        append the element to the parent.  Recurse as necessary to create
+        a deep clone of the element. """
+        ...
+    
+    def deparent(self): # -> None:
+        """ Remove ourselves from our parent node (de-parent) and return
+        the index of the parent which was deleted. """
+        ...
+    
+    def parentindex(self): # -> None:
+        """ Return the parent node index in which we live """
+        ...
+    
+    def shortrepr(self, encoding=...): # -> bytes:
+        ...
+    
+    def diffmeld(self, other): # -> dict[str, dict[str, list[Unknown]]]:
+        """ Compute the meld element differences from this node (the
+        source) to 'other' (the target).  Return a dictionary of
+        sequences in the form {'unreduced:
+               {'added':[], 'removed':[], 'moved':[]},
+                               'reduced':
+               {'added':[], 'removed':[], 'moved':[]},}
+                               """
+        ...
+    
+    def meldid(self):
+        ...
+    
+    def lineage(self): # -> list[Unknown]:
+        ...
+    
+
+
+class MeldTreeBuilder(TreeBuilder):
+    def __init__(self) -> None:
+        ...
+    
+    def start(self, tag, attrs): # -> Element:
+        ...
+    
+    def comment(self, data): # -> None:
+        ...
+    
+    def doctype(self, name, pubid, system): # -> None:
+        ...
+    
+
+
+class HTMLXMLParser(HTMLParser):
+    """ A mostly-cut-and-paste of ElementTree's HTMLTreeBuilder that
+    does special meld3 things (like preserve comments and munge meld
+    ids).  Subclassing is not possible due to private attributes. :-("""
+    def __init__(self, builder=..., encoding=...) -> None:
+        ...
+    
+    def close(self): # -> Element:
+        ...
+    
+    def handle_starttag(self, tag, attrs): # -> None:
+        ...
+    
+    def handle_endtag(self, tag): # -> None:
+        ...
+    
+    def handle_charref(self, char): # -> None:
+        ...
+    
+    def handle_entityref(self, name): # -> None:
+        ...
+    
+    def handle_data(self, data): # -> None:
+        ...
+    
+    def unknown_entityref(self, name): # -> None:
+        ...
+    
+    def handle_comment(self, data): # -> None:
+        ...
+    
+
+
+def do_parse(source, parser): # -> Element:
+    ...
+
+def parse_xml(source): # -> Element:
+    """ Parse source (a filelike object) into an element tree.  If
+    html is true, use a parser that can resolve somewhat ambiguous
+    HTML into XHTML.  Otherwise use a 'normal' parser only."""
+    ...
+
+def parse_html(source, encoding=...): # -> Element:
+    ...
+
+def parse_xmlstring(text): # -> Element:
+    ...
+
+def parse_htmlstring(text, encoding=...): # -> Element:
+    ...
+
+attrib_needs_escaping = ...
+cdata_needs_escaping = ...
+_HTMLTAGS_UNBALANCED = ...
+_HTMLTAGS_NOESCAPE = ...
+_HTMLATTRS_BOOLEAN = ...
+_NONENTITY_RE = ...
+_XML_DECL_RE = ...
+_BEGIN_TAG_RE = ...
+def insert_doctype(data, doctype=...):
+    ...
+
+def insert_meld_ns_decl(data):
+    ...
+
+def prefeed(data, doctype=...):
+    ...
+
+def sharedlineage(srcelement, tgtelement): # -> bool:
+    ...
+
+def diffreduce(elements): # -> list[Unknown]:
+    ...
+
+def intersection(S1, S2): # -> list[Unknown]:
+    ...
+
+def melditerator(element, meldid=..., _MELD_ID=...): # -> Generator[Unknown, None, None]:
+    ...
+
+_NON_ASCII_MIN = ...
+_NON_ASCII_MAX = ...
+_escape_map = ...
+_namespace_map = ...
+_pattern = ...
+def fixtag(tag, namespaces): # -> tuple[str, tuple[str, str | Unknown] | None]:
+    ...
diff --git a/manager/typings/supervisor/tests/__init__.pyi b/manager/typings/supervisor/tests/__init__.pyi
new file mode 100644 (file)
index 0000000..cea7ef9
--- /dev/null
@@ -0,0 +1,3 @@
+"""
+This type stub file was generated by pyright.
+"""
diff --git a/manager/typings/supervisor/web.pyi b/manager/typings/supervisor/web.pyi
new file mode 100644 (file)
index 0000000..3ac195f
--- /dev/null
@@ -0,0 +1,87 @@
+"""
+This type stub file was generated by pyright.
+"""
+
+class DeferredWebProducer:
+    """ A medusa producer that implements a deferred callback; requires
+    a subclass of asynchat.async_chat that handles NOT_DONE_YET sentinel """
+    CONNECTION = ...
+    def __init__(self, request, callback) -> None:
+        ...
+    
+    def more(self): # -> Type[NOT_DONE_YET] | Literal[''] | None:
+        ...
+    
+    def sendresponse(self, response): # -> None:
+        ...
+    
+
+
+class ViewContext:
+    def __init__(self, **kw) -> None:
+        ...
+    
+
+
+class MeldView:
+    content_type = ...
+    delay = ...
+    def __init__(self, context) -> None:
+        ...
+    
+    def __call__(self): # -> Type[NOT_DONE_YET]:
+        ...
+    
+    def render(self): # -> None:
+        ...
+    
+    def clone(self):
+        ...
+    
+
+
+class TailView(MeldView):
+    def render(self): # -> str:
+        ...
+    
+
+
+class StatusView(MeldView):
+    def actions_for_process(self, process): # -> list[dict[str, str | Unknown | None] | dict[str, str | Unknown]] | list[dict[str, str | Unknown | None] | dict[str, str | Unknown] | None]:
+        ...
+    
+    def css_class_for_state(self, state): # -> Literal['statusrunning', 'statuserror', 'statusnominal']:
+        ...
+    
+    def make_callback(self, namespec, action): # -> () -> str | () -> (Type[NOT_DONE_YET] | str) | () -> Unknown | () -> (str | Type[NOT_DONE_YET] | Unknown) | () -> (Type[NOT_DONE_YET] | Unknown):
+        ...
+    
+    def render(self): # -> Type[NOT_DONE_YET] | str:
+        ...
+    
+
+
+class OKView:
+    delay = ...
+    def __init__(self, context) -> None:
+        ...
+    
+    def __call__(self): # -> dict[str, str]:
+        ...
+    
+
+
+VIEWS = ...
+class supervisor_ui_handler:
+    IDENT = ...
+    def __init__(self, supervisord) -> None:
+        ...
+    
+    def match(self, request): # -> bool | None:
+        ...
+    
+    def handle_request(self, request): # -> None:
+        ...
+    
+    def continue_request(self, data, request): # -> None:
+        ...
diff --git a/manager/typings/supervisor/xmlrpc.pyi b/manager/typings/supervisor/xmlrpc.pyi
new file mode 100644 (file)
index 0000000..879c49b
--- /dev/null
@@ -0,0 +1,174 @@
+"""
+This type stub file was generated by pyright.
+"""
+
+from supervisor.compat import httplib, xmlrpclib
+from supervisor.medusa.xmlrpc_handler import xmlrpc_handler
+
+class Faults:
+    UNKNOWN_METHOD = ...
+    INCORRECT_PARAMETERS = ...
+    BAD_ARGUMENTS = ...
+    SIGNATURE_UNSUPPORTED = ...
+    SHUTDOWN_STATE = ...
+    BAD_NAME = ...
+    BAD_SIGNAL = ...
+    NO_FILE = ...
+    NOT_EXECUTABLE = ...
+    FAILED = ...
+    ABNORMAL_TERMINATION = ...
+    SPAWN_ERROR = ...
+    ALREADY_STARTED = ...
+    NOT_RUNNING = ...
+    SUCCESS = ...
+    ALREADY_ADDED = ...
+    STILL_RUNNING = ...
+    CANT_REREAD = ...
+
+
+def getFaultDescription(code): # -> str:
+    ...
+
+class RPCError(Exception):
+    def __init__(self, code, extra=...) -> None:
+        ...
+    
+    def __str__(self) -> str:
+        ...
+    
+
+
+class DeferredXMLRPCResponse:
+    """ A medusa producer that implements a deferred callback; requires
+    a subclass of asynchat.async_chat that handles NOT_DONE_YET sentinel """
+    CONNECTION = ...
+    def __init__(self, request, callback) -> None:
+        ...
+    
+    def more(self): # -> Type[NOT_DONE_YET] | Literal[''] | None:
+        ...
+    
+    def getresponse(self, body): # -> None:
+        ...
+    
+
+
+def xmlrpc_marshal(value):
+    ...
+
+class SystemNamespaceRPCInterface:
+    def __init__(self, namespaces) -> None:
+        ...
+    
+    def listMethods(self): # -> list[Unknown]:
+        """ Return an array listing the available method names
+
+        @return array result  An array of method names available (strings).
+        """
+        ...
+    
+    def methodHelp(self, name):
+        """ Return a string showing the method's documentation
+
+        @param string name   The name of the method.
+        @return string result The documentation for the method name.
+        """
+        ...
+    
+    def methodSignature(self, name): # -> List[Unknown]:
+        """ Return an array describing the method signature in the
+        form [rtype, ptype, ptype...] where rtype is the return data type
+        of the method, and ptypes are the parameter data types that the
+        method accepts in method argument order.
+
+        @param string name  The name of the method.
+        @return array result  The result.
+        """
+        ...
+    
+    def multicall(self, calls): # -> (remaining_calls: Unknown = remaining_calls, callbacks: Unknown = callbacks, results: Unknown = results) -> (Type[NOT_DONE_YET] | Unknown) | Type[NOT_DONE_YET] | list[Unknown]:
+        """Process an array of calls, and return an array of
+        results. Calls should be structs of the form {'methodName':
+        string, 'params': array}. Each result will either be a
+        single-item array containing the result value, or a struct of
+        the form {'faultCode': int, 'faultString': string}. This is
+        useful when you need to make lots of small calls without lots
+        of round trips.
+
+        @param array calls  An array of call requests
+        @return array result  An array of results
+        """
+        ...
+    
+
+
+class AttrDict(dict):
+    def __getattr__(self, name): # -> None:
+        ...
+    
+
+
+class RootRPCInterface:
+    def __init__(self, subinterfaces) -> None:
+        ...
+    
+
+
+def capped_int(value): # -> int:
+    ...
+
+def make_datetime(text): # -> datetime:
+    ...
+
+class supervisor_xmlrpc_handler(xmlrpc_handler):
+    path = ...
+    IDENT = ...
+    unmarshallers = ...
+    def __init__(self, supervisord, subinterfaces) -> None:
+        ...
+    
+    def loads(self, data): # -> tuple[tuple[Any, ...] | None, Any | None]:
+        ...
+    
+    def match(self, request):
+        ...
+    
+    def continue_request(self, data, request): # -> None:
+        ...
+    
+    def call(self, method, params): # -> Any:
+        ...
+    
+
+
+def traverse(ob, method, params): # -> Any:
+    ...
+
+class SupervisorTransport(xmlrpclib.Transport):
+    """
+    Provides a Transport for xmlrpclib that uses
+    httplib.HTTPConnection in order to support persistent
+    connections.  Also support basic auth and UNIX domain socket
+    servers.
+    """
+    connection = ...
+    def __init__(self, username=..., password=..., serverurl=...) -> None:
+        ...
+    
+    def close(self): # -> None:
+        ...
+    
+    def request(self, host, handler, request_body, verbose=...):
+        ...
+    
+
+
+class UnixStreamHTTPConnection(httplib.HTTPConnection):
+    def connect(self): # -> None:
+        ...
+    
+
+
+def gettags(comment): # -> list[Unknown]:
+    """ Parse documentation strings into JavaDoc-like tokens """
+    ...