fix: handle missing environment variables more robustly
This commit is contained in:
parent
fcf7254e64
commit
3b7ddfa718
4 changed files with 61 additions and 7 deletions
12
pdm.lock
12
pdm.lock
|
@ -5,7 +5,7 @@
|
||||||
groups = ["default", "all", "dev"]
|
groups = ["default", "all", "dev"]
|
||||||
strategy = ["cross_platform"]
|
strategy = ["cross_platform"]
|
||||||
lock_version = "4.4.1"
|
lock_version = "4.4.1"
|
||||||
content_hash = "sha256:17fdf74bf4b2980c66c106cebcb936921e671e8adaeb2f2e95d95f255704aa38"
|
content_hash = "sha256:73f93e2fcfc5fc5af9acfdc9604b9f27d3eb5d9c6012f600c671523c2f78bf4c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aiocache"
|
name = "aiocache"
|
||||||
|
@ -80,6 +80,16 @@ files = [
|
||||||
{file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"},
|
{file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "boltons"
|
||||||
|
version = "24.0.0"
|
||||||
|
requires_python = ">=3.7"
|
||||||
|
summary = "When they're not builtins, they're boltons."
|
||||||
|
files = [
|
||||||
|
{file = "boltons-24.0.0-py3-none-any.whl", hash = "sha256:9618695a6ec4f50412e7072e5d78910a00b4111d0b9b549e4a3d60bc321e7807"},
|
||||||
|
{file = "boltons-24.0.0.tar.gz", hash = "sha256:7153feccaea1ff2e1472f68d4b57fadb796a2ee49d29f638f1c9cd8fb5ebd916"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "class-doc"
|
name = "class-doc"
|
||||||
version = "0.2.6"
|
version = "0.2.6"
|
||||||
|
|
|
@ -14,6 +14,7 @@ dependencies = [
|
||||||
"pytomlpp>=1.0.13",
|
"pytomlpp>=1.0.13",
|
||||||
"apischema>=0.18.1",
|
"apischema>=0.18.1",
|
||||||
"yarl>=1.9.4",
|
"yarl>=1.9.4",
|
||||||
|
"boltons>=24.0.0",
|
||||||
]
|
]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,32 @@
|
||||||
|
from collections.abc import Mapping
|
||||||
from os import environ
|
from os import environ
|
||||||
|
from typing import TypeVar
|
||||||
|
|
||||||
from apischema import deserialize
|
from apischema import deserialize
|
||||||
|
from boltons.iterutils import remap
|
||||||
from pytomlpp import load
|
from pytomlpp import load
|
||||||
from xdg_base_dirs import xdg_config_home
|
from xdg_base_dirs import xdg_config_home
|
||||||
|
|
||||||
from .model import Config
|
from .model import Config
|
||||||
|
|
||||||
__all__ = "loadConfig"
|
__all__ = ("loadConfig",)
|
||||||
|
K = TypeVar("K")
|
||||||
|
V = TypeVar("V")
|
||||||
|
|
||||||
|
|
||||||
|
# Sadly this is the kind of function that's incredibly easy to type statically
|
||||||
|
# in something like TypeScript, but apparently impossible to type statically in
|
||||||
|
# Python. Basically the TypeScript typing looks like this:
|
||||||
|
# type OmitNulls<T> = {[K in keyof T]?: OmitNulls<NonNullable<T[K]>>};
|
||||||
|
# function withoutNulls<T>(data: T): OmitNulls<T>;
|
||||||
|
# But the Python type system (currently?) lacks mapped and index types, so you
|
||||||
|
# can't recurse over an arbitrary type's structure as OmitNulls<T> does, as
|
||||||
|
# well as conditional types so you can't easily define a "filtering" utility
|
||||||
|
# type like NonNullable<T>. Python's type system also doesn't infer a
|
||||||
|
# dictionary literal as having a structural type by default in the way
|
||||||
|
# TypeScript does, of course, so that part wouldn't work anyway.
|
||||||
|
def withoutNones(data: Mapping[K, V | None]) -> Mapping[K, V]:
|
||||||
|
return remap(data, lambda p, k, v: v is not None)
|
||||||
|
|
||||||
|
|
||||||
def loadConfigFromFile() -> Config:
|
def loadConfigFromFile() -> Config:
|
||||||
|
@ -17,14 +37,14 @@ def loadConfigFromFile() -> Config:
|
||||||
|
|
||||||
|
|
||||||
def loadConfigFromEnv() -> Config:
|
def loadConfigFromEnv() -> Config:
|
||||||
port = int(environ["MPD_PORT"]) if "MPD_PORT" in environ else None
|
port = int(environ.pop("MPD_PORT")) if "MPD_PORT" in environ else None
|
||||||
host = environ.get("MPD_HOST")
|
host = environ.pop("MPD_HOST", None)
|
||||||
password = environ.get("MPD_PASSWORD")
|
password = environ.pop("MPD_PASSWORD", None)
|
||||||
cache = environ.get("MPD_NOW_PLAYABLE_CACHE")
|
cache = environ.pop("MPD_NOW_PLAYABLE_CACHE", None)
|
||||||
if password is None and host is not None and "@" in host:
|
if password is None and host is not None and "@" in host:
|
||||||
password, host = host.split("@", maxsplit=1)
|
password, host = host.split("@", maxsplit=1)
|
||||||
data = {"cache": cache, "mpd": {"port": port, "host": host, "password": password}}
|
data = {"cache": cache, "mpd": {"port": port, "host": host, "password": password}}
|
||||||
return deserialize(Config, data)
|
return deserialize(Config, withoutNones(data))
|
||||||
|
|
||||||
|
|
||||||
def loadConfig() -> Config:
|
def loadConfig() -> Config:
|
||||||
|
|
23
stubs/boltons/iterutils.pyi
Normal file
23
stubs/boltons/iterutils.pyi
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
from collections.abc import Callable, Mapping
|
||||||
|
from typing import TypeAlias, TypeVar
|
||||||
|
|
||||||
|
# Apparently you need Python 3.13 for type var defaults to work. But since this
|
||||||
|
# is just a stub file, it's okay if they aren't supported at runtime.
|
||||||
|
KIn = TypeVar("KIn")
|
||||||
|
KOut = TypeVar("KOut", default=KIn)
|
||||||
|
VIn = TypeVar("VIn")
|
||||||
|
VOut = TypeVar("VOut", default=VIn)
|
||||||
|
|
||||||
|
Path: TypeAlias = tuple[KIn, ...]
|
||||||
|
|
||||||
|
# remap() is Complicated and really difficult to define a type for, so I'm not
|
||||||
|
# surprised the boltons package doesn't try to type it for you. This particular
|
||||||
|
# type declaration works fine for my use of the function, but it's actually
|
||||||
|
# vastly more flexible than that - it'll accept any iterable as the root, not
|
||||||
|
# just mappings, and you can provide "enter" and "exit" callables to support
|
||||||
|
# arbitrary data structures.
|
||||||
|
def remap(
|
||||||
|
root: Mapping[KIn, VIn],
|
||||||
|
visit: Callable[[Path[KIn], KIn, VIn], tuple[KOut, VOut] | bool],
|
||||||
|
reraise_visit: bool = False,
|
||||||
|
) -> Mapping[KOut, VOut]: ...
|
Loading…
Reference in a new issue