Everything the SDK reads
EverySTANDIN_ variable, in one place, because configuring a deployment out of nine pages is how a deployment ends up half configured.
Every variable above except
STANDIN_SHOW_ROOTS is read by the TypeScript SDK under the same name and with the same meaning, so a deployment that runs both workers configures both the same way. Two of the names are exported as constants rather than written out, in both SDKs, so your code and the SDK cannot drift apart:
Three things that cost an afternoon
- A blank
STANDIN_CHAT_SECRETfalls through here, and does not in TypeScript.ChatChannelreads thesecret=argument, thenSTANDIN_CHAT_SECRET, thenSTANDIN_SECRET, and in Python an exported empty string is skipped like an unset one. The TypeScript twin stops at the first variable that is merely present, soexport STANDIN_CHAT_SECRET=shadows a perfectly goodSTANDIN_SECRETthere and the channel refuses to construct. Unset the variable rather than blanking it, and the two behave alike. STANDIN_PORTis read withint(). A value that is not a number raisesValueErrorwhileCallServeris being built, and the message names the bad value rather than the variable it came from, so check a templated value before you export it. The TypeScript twin coerces instead and carriesNaNas far asstart().- A
STANDIN_WS_PATHthat reduces to/is refused withws_path must be a real path such as /msteams/calling. A worker listening at the root answers anything that reaches it, so this fails at construction rather than on the first call.
STANDIN_SHOW_ROOTS governs what may be drawn onto the tile in a call, STANDIN_MEDIA_ROOTS governs what may be uploaded into a chat. Both default to empty, because the paths reaching them were chosen by a model that somebody is steering.
Reading your own
Writing a plugin means reading the same five things every time: a value that must be set, one that may be, a boolean, a check that a vendor host is really that vendor’s, and a header map. Use these rather than rolling your own, and the error somebody sees for a missing key reads the same whichever provider they picked. That is the whole reason the module exists: the messages had drifted once already, one wording per provider.These five are not re-exported at the package root in Python:
from standin import required is an ImportError. Import them from standin.config. The TypeScript twin differs on both counts: it exports required, optional, flag, vendorHost and jsonObject from the package root, in camel case. Knowing that here is cheaper than discovering it as a failed import while porting a plugin.
Every failure is a
StandInError, which is at the package root: from standin import StandInError.
purpose completes the sentence “X is required to …”, so write it as a verb phrase. The point is that the reader learns what they were trying to do, not only which string was empty.
Why a flag accepts only true
flag trims and lowercases the value and compares it to the single string true. TRUE and True are therefore true, and 1, yes, on and y are all false.
That is deliberately strict, and the direction matters more than the strictness. The settings worth a flag in this SDK are guards, and a guard must not be turned off by a typo. If somebody writes yes where true was meant, the value reads as false: the guard stays in whatever state the default gives it rather than being switched by a string nobody validated. An unset variable is not a typo, so it returns default untouched.
Why a vendor host is pinned
Why a header map is never logged
json_object parses a JSON object and coerces every key and value to str. Anything that is not a JSON object, including a valid JSON array or number, raises the same message.
That message names the variable and stops. It never echoes the value, and neither does anything else on the failure path, because this helper exists for header maps: the value carries your credentials to somebody else’s endpoint. A parse error that pretty-prints the malformed value is a bearer token in a log file, on the one code path most likely to be run with debug logging turned up.
A missing extra is not a broken install
One package holds the SDK and every plugin, soimport standin sits above code that references frameworks you may never install. Nothing under standin.plugins imports its framework while loading, and import standin imports no plugin at all. When you do touch one whose framework is absent, you get this rather than a bare traceback from inside somebody else’s package:
plugin, module and extra as attributes, and it subclasses both StandInError and ImportError. The ImportError half is the one worth recording: except ImportError, importlib probes and pytest.importorskip all treat a missing extra as a missing import, which is exactly what it is, so a plugin that is absent degrades the same way any optional import does rather than escaping as an unfamiliar error class.
It is also raised only when the framework root is what went missing. A framework that is installed but broken raises its own ModuleNotFoundError for its own dependency, and that propagates untouched: telling somebody to run pip install "standin-sdk[livekit]" when LiveKit is already installed sends them to fix something that is not broken.
The real extras, from the package manifest:
tile and render are not plugins, so a missing one does not raise PluginNotInstalled. jpeg_encoder() returns None after logging a line that names the extra, and the tile relay simply does not run while audio is unaffected; rendering a PDF page without the extra raises StandInError with showing a PDF needs the render extra, followed by that install line. Both are optional because most deployments never put their own video on the tile and never show a file, and a PDF engine in every install is the wrong trade.
The SDK requires Python 3.10 or newer.
Version
standin-sdk distribution and the imported module can never disagree about which version you are running. Quote it in a bug report: the wire protocol is versioned separately, and knowing both is what separates “your worker is old” from “the message was wrong”. The TypeScript twin exports its own as VERSION.
Next
CallServer
The listener most of these variables configure.
Checking the install
Prove the configuration is right before a real call does.