indikit.settings and indikit.logging_config¶
The INDIKIT_* environment, and the logging INDIkit configures at an entrypoint.
Settings is the only reader of the INDIKIT_* environment inside the package: no
command-line option carries its own lookup, so a variable has one meaning and one place
documenting it. Where a setting also has a flag - --log-level, --wire, --token,
--allow-origin, --allow-insecure-bind - the flag wins and its absence defers to the
environment.
The one reader outside the package is the Docker image's entrypoint, which reads
INDIKIT_TOKEN, INDIKIT_ALLOW_INSECURE_BIND and INDIKIT_ALLOWED_ORIGINS as
fallbacks for its own WEB_* spellings, because it has to generate a token and print the
panel's URL with it before serve starts. It passes what it resolved on as flags, so
Settings still decides one value. See Docker for both spellings.
Nothing reads either module implicitly: IndiClient, Bridge and create_app take
explicit parameters, and the entrypoints - the indikit callback and
indikit.driver.run - read the settings and pass the values down. Logging goes to
stderr, because a driver's stdout is the INDI wire.
log_level accepts CRITICAL, ERROR, WARNING, INFO or DEBUG, case-insensitive;
anything else is a usage error rather than a traceback out of uvicorn.Config. Wire
traffic goes to one logger for all four sites, named by WIRE_LOGGER
("indikit.wire").
indikit.settings ¶
INDIKIT_*: the environment an operator configures INDIkit with.
A container is configured by environment, not by editing the command line inside
it, so the knobs an operator actually reaches for - how chatty the log is, how
long the client waits for indiserver, what token guards the write surface -
are readable from the environment under one prefix. :class:Settings is that
reader, and it is the only one: nothing else in the package calls
:func:os.environ.get, and no flag carries a Typer envvar= any more. One
variable, one reader, one place to look up what it means.
Nothing reads this implicitly. :class:~indikit.client.IndiClient,
:class:~indikit.web.Bridge and :func:~indikit.web.create_app keep
explicit parameters with their present defaults; the entrypoints - the CLI
callback and :func:indikit.driver.run - read the settings and pass the
values down. That is what keeps a library import free of ambient environment and
keeps every object injectable, which is what the whole test suite and
create_app(client=...) depend on.
This module imports nothing from driver/, web/ or client/. It is
imported by both the CLI and the driver's run(), so any dependency it took
would become an edge in the import graph that tests/test_layering.py holds
flat.
Some of these settings also have a flag, and the rule between the two is one
rule everywhere: an explicit flag beats the environment, and the flag's
absence is what defers to it. :mod:indikit.cli spells that out by giving
every such option a None default and resolving it in the command body, so
the environment is read when the command runs rather than when the module is
imported - a default evaluated at import time would freeze the first value the
process ever saw and make the precedence a lie under any in-process test runner.
:attr:Settings.model_config sets extra="ignore", and that is load-bearing
rather than tidy. The prefix is not reserved for this model: the test suite
already ships INDIKIT_UPDATE_GOLDEN, and an operator's own tooling may set
anything. Under pydantic's default forbid, one such name in the environment
would raise out of :func:settings and take every entrypoint down - the CLI,
the bridge and every driver at once.
LogLevel ¶
Bases: StrEnum
The log levels INDIKIT_LOG_LEVEL and --log-level accept.
A closed set rather than a free string, because the level is passed on to
uvicorn as well as to :mod:logging, and uvicorn knows only these five. A
typo is then a parse error naming the variable instead of a stack trace out
of uvicorn.Config.
Settings ¶
Bases: BaseSettings
The INDIKIT_* environment, parsed and typed.
Every default is the value the code already used before this model existed,
so reading the settings changes no behaviour on its own. They are the
defaults for the flags too, since the flags have none of their own: an
option that also names a variable defaults to None and falls through to
the field below, which is what stops a flag's default and a variable's from
drifting apart.
Attributes:
| Name | Type | Description |
|---|---|---|
log_level |
LogLevel
|
|
wire_log |
bool
|
|
connect_timeout |
float
|
|
reconnect_delay |
float
|
|
message_history |
int
|
|
max_backlog |
int
|
|
token |
str
|
|
allowed_origins |
tuple of str
|
|
allow_insecure_bind |
bool
|
|
config_dir |
Path or None
|
|
settings
cached
¶
settings() -> Settings
Return the process's settings, read from the environment once.
Cached because the environment does not change under a running process and
every entrypoint would otherwise re-parse it. A test that manipulates the
environment clears the cache with settings.cache_clear().
Returns:
| Name | Type | Description |
|---|---|---|
settings |
Settings
|
The parsed |
Raises:
| Type | Description |
|---|---|
ValidationError
|
Raised if a variable is present but does not parse as its type; the error names the variable. |
Source code in src/indikit/settings.py
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 | |
indikit.logging_config ¶
The package's logging setup, and the shared indikit.wire logger.
:func:configure_logging is the only place INDIkit installs a log handler,
and it is called from entrypoints alone - the CLI callback and
:func:indikit.driver.run. A library import configures nothing, because a
library that installs a root handler on import steals the application's logging.
Logs go to stderr, and that is a requirement rather than a default. A
driver's stdout is the INDI wire: :func:indikit.driver.runtime._open_stdio
writes serialised XML straight to sys.stdout.buffer, so a log line there
corrupts the stream indiserver is parsing. indiserver relays a driver's
stderr into its own log, so stderr is also where the operator will find it.
The wire logger. indikit.wire is one name for one question - "what is
actually on the wire" - and four call sites answer it: the client's reader and
writer, and the driver runtime's reader and writer. An operator should not have
to learn that those are different modules, which is why they do not log this on
their own module loggers. :func:log_wire guards on
:meth:logging.Logger.isEnabledFor, so a run with wire logging off pays one flag
check per message.
A BLOB's payload is never logged. One frame is megabytes, and rendering it would make the log the slowest thing in the process; the line reports the payload size read off the model instead, never off a copy made for the log.
configure_logging ¶
configure_logging(level: str | int = 'INFO', *, wire: bool = False) -> None
Send INDIkit logging to stderr at level. Call from an entrypoint only.
Safe to call twice: the handler is installed once and kept, so a later call changes the levels without doubling every line.
The handler is installed outright rather than through
:func:logging.basicConfig, which defers to a root that already has a
handler. Deferring reads well and is wrong here: under a test runner, or in
any process that touched :mod:logging first, it would quietly install
nothing and the stderr guarantee above - the one that keeps a driver's
stdout clean - would hold only when nothing else got there first.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
level
|
str or int
|
A standard :mod: |
'INFO'
|
wire
|
bool
|
Whether to turn the shared |
False
|
Raises:
| Type | Description |
|---|---|
ValueError
|
Raised if |
Source code in src/indikit/logging_config.py
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | |
log_wire ¶
log_wire(direction: str, msg: IndiMessage, nbytes: int | None = None) -> None
Log one INDI message on the shared wire logger, if wire logging is on.
The message is named by its model tag (def, set, new,
getProperties, ...) rather than by the XML element it would serialise to.
The same message travels as XML upstream and as JSON to a browser, so the
XML element name would be wrong for half of what this logger reports, and
reproducing the codec's tag-stem rule here would put a fourth copy of it in
the package.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
direction
|
str
|
|
required |
msg
|
IndiMessage
|
The message to describe. |
required |
nbytes
|
int
|
The serialised size, where the caller knows it - the writers do, the readers do not, because the parser frames a chunk into several messages. |
None
|
Source code in src/indikit/logging_config.py
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | |