making it easy to expand daemons via minimal changes
structured config mapping & data-driven system
Yes — the pattern you have now works, but it locks you into hardcoding each daemon. Since you already have a config with VTRC_DAEMONS, you can make this fully data-driven so adding/removing daemons doesn’t require touching your Python code.
Here’s a more expandable approach:
1. Use a structured config mapping
Instead of keeping RPC connection parameters scattered as individual variables, you can organize them in a dict keyed by daemon name. Since your environment variables already follow a pattern, you can dynamically load them.
import os
# list of daemons to enable
VTRC_DAEMONS = [
"verusd",
"verusdvarrr",
# "verusdvdex", # can enable/disable by commenting
]
# function to load connection details from env using prefix
def load_rpc_config(name: str):
prefix = name.replace("verusd", "").replace("verus", "") or name
return {
"user": os.environ[f"{prefix}_rpc_user"],
"password": os.environ[f"{prefix}_rpc_password"],
"port": os.environ[f"{prefix}_rpc_port"],
"host": os.environ[f"{prefix}_rpc_host"],
}
# full config dict
DAEMON_CONFIGS = {daemon: load_rpc_config(daemon) for daemon in VTRC_DAEMONS}
2. Use it in your setup_rpc_connections
Now your setup method can be generic:
def setup_rpc_connections(self):
"""Set up RPC connections for all configured daemons."""
self.daemonrpc = {}
for daemon_name, cfg in DAEMON_CONFIGS.items():
self.daemonrpc[daemon_name] = NodeRpc(
cfg["user"],
cfg["password"],
cfg["port"],
cfg["host"],
)
3. Benefits
- Add new daemon → only touch
VTRC_DAEMONSand environment variables, no Python changes. - Centralized config → all RPC connection details live in one place (
DAEMON_CONFIGS). - Scalable → if you end up with 10–20 daemons, your Python code doesn’t grow — only the config changes.
👉 Question: do you want to keep the daemon constants (VTRC_VERUSD, VTRC_VERUSDVARRR, etc.), or would you prefer using plain strings ("verusd", "verusdvarrr") everywhere?
If you want to keep the constants for clarity, I can show you how to wire them into this setup without hardcoding.
using daemon constants
Perfect 👍 — keeping the constants is a good move for safety and IDE support.
Here’s how you can keep the constants, but still make the system data-driven:
1. Define your constants & list of enabled daemons
# constants
VTRC_VERUSD = "verusd"
VTRC_VERUSDVARRR = "verusdvarrr"
VTRC_VERUSDVDEX = "verusdvdex"
# which daemons to activate
VTRC_DAEMONS = [VTRC_VERUSD, VTRC_VERUSDVARRR]
2. Generic loader for RPC configs
We’ll derive env var names based on the daemon constant. For example:
verusd→vrsc_rpc_user(your VRSC naming convention)verusdvarrr→varrr_rpc_userverusdvdex→vdex_rpc_user
So we just need a mapping from daemon → prefix:
import os
DAEMON_ENV_PREFIX = {
VTRC_VERUSD: "vrsc",
VTRC_VERUSDVARRR: "varrr",
VTRC_VERUSDVDEX: "vdex",
}
Now we can load configs generically:
def load_rpc_config(daemon: str):
prefix = DAEMON_ENV_PREFIX[daemon]
return {
"user": os.environ[f"{prefix}_rpc_user"],
"password": os.environ[f"{prefix}_rpc_password"],
"port": os.environ[f"{prefix}_rpc_port"],
"host": os.environ[f"{prefix}_rpc_host"],
}
3. Build the configs & connections
DAEMON_CONFIGS = {
daemon: load_rpc_config(daemon) for daemon in VTRC_DAEMONS
}
def setup_rpc_connections(self):
"""Set up RPC connections for all configured daemons."""
self.daemonrpc = {
daemon: NodeRpc(
cfg["user"], cfg["password"], cfg["port"], cfg["host"]
)
for daemon, cfg in DAEMON_CONFIGS.items()
}
✅ Advantages
- Constants stay in code → IDE catches typos (
VTRC_VERUSD, not"verusd"). - Expandability → add to
VTRC_DAEMONSandDAEMON_ENV_PREFIX, no touching main logic. - Clear mapping between constants and environment variable prefixes.
Would you like me to also show you a config-file driven version (e.g. .toml or .yaml) so you don’t have to expand DAEMON_ENV_PREFIX manually each time, or do you prefer keeping it strictly in Python + env vars?
Toml vs Yaml
Got it ✅ — let’s look at both TOML and YAML approaches side by side. The main difference is just in how the config file is structured and parsed.
🔹 1. Example TOML Config (config.toml)
# Which daemons are active
active_daemons = ["verusd", "verusdvarrr"]
[daemons.verusd]
prefix = "vrsc"
rpc_user = "user1"
rpc_password = "pass1"
rpc_port = "27486"
rpc_host = "127.0.0.1"
[daemons.verusdvarrr]
prefix = "varrr"
rpc_user = "user2"
rpc_password = "pass2"
rpc_port = "27487"
rpc_host = "127.0.0.1"
[daemons.verusdvdex]
prefix = "vdex"
rpc_user = "user3"
rpc_password = "pass3"
rpc_port = "27488"
rpc_host = "127.0.0.1"
Python Loader for TOML
import tomllib # Python 3.11+, else `pip install tomli`
VTRC_VERUSD = "verusd"
VTRC_VERUSDVARRR = "verusdvarrr"
VTRC_VERUSDVDEX = "verusdvdex"
with open("config.toml", "rb") as f:
config = tomllib.load(f)
VTRC_DAEMONS = config["active_daemons"]
def setup_rpc_connections(self):
self.daemonrpc = {}
for daemon in VTRC_DAEMONS:
cfg = config["daemons"][daemon]
self.daemonrpc[daemon] = NodeRpc(
cfg["rpc_user"], cfg["rpc_password"], cfg["rpc_port"], cfg["rpc_host"]
)
🔹 2. Example YAML Config (config.yaml)
# Which daemons are active
active_daemons:
- verusd
- verusdvarrr
daemons:
verusd:
prefix: vrsc
rpc_user: user1
rpc_password: pass1
rpc_port: 27486
rpc_host: 127.0.0.1
verusdvarrr:
prefix: varrr
rpc_user: user2
rpc_password: pass2
rpc_port: 27487
rpc_host: 127.0.0.1
verusdvdex:
prefix: vdex
rpc_user: user3
rpc_password: pass3
rpc_port: 27488
rpc_host: 127.0.0.1
Python Loader for YAML
import yaml # pip install pyyaml
VTRC_VERUSD = "verusd"
VTRC_VERUSDVARRR = "verusdvarrr"
VTRC_VERUSDVDEX = "verusdvdex"
with open("config.yaml", "r") as f:
config = yaml.safe_load(f)
VTRC_DAEMONS = config["active_daemons"]
def setup_rpc_connections(self):
self.daemonrpc = {}
for daemon in VTRC_DAEMONS:
cfg = config["daemons"][daemon]
self.daemonrpc[daemon] = NodeRpc(
cfg["rpc_user"], cfg["rpc_password"], cfg["rpc_port"], cfg["rpc_host"]
)
🔹 3. Benefits of TOML vs YAML
- TOML → strict, less error-prone, great for configs (used in Python packaging).
- YAML → more flexible, widely used (esp. for infra like Docker/K8s), but indentation mistakes can bite you.
👉 Do you prefer embedding sensitive values (user/password) in the TOML/YAML, or would you rather pull them from environment variables (e.g. rpc_password = ${ENV_VAR} style)?
I can show you how to support env var expansion in both cases if you want to keep secrets out of the config file.