Repository files navigation

NavConfig

NavConfig is a configuration management library for Python projects. It is the default configuration layer of the Navigator Framework, but it works perfectly as a stand-alone tool for any Python application.

NavConfig can load configuration directives from multiple sources (and combine them):

  • Environment files (.env)
  • INI files (via configparser)
  • TOML and YAML files
  • pyproject.toml
  • Redis
  • HashiCorp Vault
  • Python settings modules (settings/settings.py)

The main goal of NavConfig is to centralize configuration access through a single, immutable point of truth that can be shared across modules.

Documentation: https://phenobarbital.github.io/navconfig/

Motivation

Applications require many configuration options. Some of those options hold secrets or credentials and must be kept separate from general settings. Configuration also varies between environments (development, staging, production).

NavConfig addresses this by loading secrets from .env files and structured settings from INI/TOML/YAML files, keeping concerns separated. It also supports retrieving configuration from external stores such as Redis or HashiCorp Vault.

Installation

pip install navconfig

To include optional backends:

# Redis support
pip install navconfig[redis]
# HashiCorp Vault support
pip install navconfig[hvac]
# Logstash logging
pip install navconfig[logstash]
# All features
pip install navconfig[all]

The kardex CLI

NavConfig ships a command line tool called kardex that bootstraps and maintains the configuration layout of a project. It is organised in command groups, each with its own actions:

CommandWhat it does
kardex env createCreate the default project structure.
kardex env create --splitSame, plus the supplementary .env.* files.
kardex env new <name>Add an environment from the shared env/.env.
kardex vault createWrite the HashiCorp Vault directives into env/<env>/.env.
kardex vault migratePush the variables of an environment into Vault.
kardex vault save VAR:VALUEStore one or more variables in Vault.
kardex log enableEnable the [logging] section of etc/config.ini.

Run kardex <group> <action> --help for the full list of options.

Upgrading from 2.x:kardex create and kardex new-env were removed in 3.0. Use kardex env create and kardex env new instead.

Quickstart

1. Create the project structure

kardex env create --env dev

This generates the following structure in the current directory:

.
|-- env/
| |-- .env (shared across environments, no ENV= pin)
| +-- dev/
| +-- .env (pinned to ENV=dev)
|-- etc/
| +-- config.ini
|-- logs/
+-- templates/
  • env/.env -- values shared by every environment. kardex env new uses it as the template for new environments.
  • env/dev/.env -- environment variables (secrets, feature flags, paths).
  • etc/config.ini -- INI-based settings consumed by NavConfig, including a [logging] section.
  • logs/ -- default directory where rotating log files are written.
  • templates/ -- default directory for template files.

Existing files are never overwritten; pass --force when you do want them replaced. Use --path to point at a different project root:

kardex env create --env dev --path /srv/myapp

2. Split the configuration across several files (optional)

NavConfig loads .env first and then a set of supplementary files, which keeps unrelated concerns apart. Pass --split to create them all:

kardex env create --env dev --split
env/dev/
|-- .env base configuration and Vault credentials
|-- .env.resources paths and resource-level directives
|-- .env.databases database connection settings
|-- .env.api HTTP layer settings
|-- .env.cache Redis / cache backend settings
+-- .env.local local overrides, loaded last (keep out of git)

Files are loaded in that order, so .env.local always wins. Everything ends up in the same flat namespace, so config.get("DBHOST") works regardless of which file declared it.

3. Add more environments

kardex env new prod
kardex env new staging --split

This copies env/.env (or the bundled sample if no shared file exists) into env/<name>/.env, adjusting the ENV variable automatically.

4. Select an environment

Set the ENV variable before starting your application:

ENV=prod python app.py

NavConfig loads env/prod/.env and any INI file referenced by its CONFIG_FILE directive.

HashiCorp Vault

Configure the connection

kardex vault create --env dev \
--url http://vault.internal:8200 \
--token "$VAULT_TOKEN" \
--mount-point myapp

This appends a delimited block to env/dev/.env (creating the file if it does not exist yet) with the directives NavConfig reads:

VAULT_ENABLED=true
VAULT_URL=http://vault.internal:8200
VAULT_TOKEN=...
VAULT_MOUNT_POINT=myapp
VAULT_VERSION=2
# VAULT_ENV=

Secrets are then read from <VAULT_MOUNT_POINT>/<ENV>/, and merged on top of the file-based values. Set VAULT_ENV to read from a different path segment than ENV; set NAVCONFIG_FILE_OVERRIDE_ENABLED=true to let the .env files win over Vault instead.

Re-running the command updates only the directives you pass on the command line, which makes token rotation a one-liner. Pass --force to rewrite the whole block.

Migrate an existing .env into Vault

kardex vault migrate --env dev --dry-run # inspect first
kardex vault migrate --env dev

Two families of variables are deliberately left in the .env file, because NavConfig needs them before it can reach Vault:

  • the Vault directives themselves (VAULT_*), and
  • the bootstrap directives (ENV, CONFIG_FILE, SITE_ROOT, ...), which can be included anyway with --include-bootstrap.

Useful options: --include-extra (also migrate the .env.* files), --keep-existing (never overwrite a key already stored in Vault), --file (migrate an arbitrary file) and --yes (skip the confirmation prompt).

Values are masked in the output, and nothing is written until you confirm.

Store single variables

kardex vault save DB_PASSWORD:s3cr3t
kardex vault save "DSN:postgres://user:pass@host:5432/db" API_KEY:abc123

Only the first colon separates the name from the value, so values may contain colons themselves.

Logging

Enable the logging facility with:

kardex log enable

That writes the [logging] section of etc/config.ini (creating the file from the bundled sample when missing) with console and rotating-file output enabled, and creates the log directory. Comments in the INI file are kept.

To also forward records to a Logstash server:

kardex log enable --logstash \
--logstash-host logs.internal \
--logstash-port 5044 \
--logstash-level INFO

The Logstash handler requires pip install navconfig[logstash].

Other options: --loglevel, --logdir, --quiet (no console output), --no-file (no rotating file handler) and --mailer (email alerts on CRITICAL records).

Apply the resulting configuration in your application:

importloggingfromlogging.configimportdictConfigfromnavconfig.loggingimportlogging_configdictConfig(logging_config)
logger=logging.getLogger("MY_APP")
logger.info("Hello World")

Console output uses colored formatting by default:

[INFO] 2024-03-11 19:31:39,408 MY_APP: Hello World

Accessing configuration

fromnavconfigimportconfigAPP_NAME=config.get("APP_NAME")
# "MyApp"

Attribute-style access also works:

APP_NAME=config.APP_NAME

Typed accessors

config.get("APP_NAME") # strconfig.getint("PORT", fallback=8080) # intconfig.getboolean("DEBUG") # boolconfig.getlist("ALLOWED_HOSTS") # list (comma-separated)config.getdict("EXTRA") # dict

An optional fallback argument is returned when the key is not found:

config.get("MISSING_KEY", "default_value")

Initialization

NavConfig resolves the project layout and loads the environment the first time one of its package-level names is accessed (config, BASE_DIR, DEBUG, ENV, ...), not while the package is being imported. Call navconfig.bootstrap() when you need the environment loaded into os.environ as a side effect without touching any of those names:

importnavconfignavconfig.bootstrap()

Configuration directories

By default NavConfig looks for files relative to the project root:

File typeDefault location
.envenv/ (plus ENV subdirectory)
.yml / .tomlenv/
pyproject.tomlproject root
.inietc/

A typical project looks like this:

myapp/
|-- __init__.py
|-- pyproject.toml
|-- env/
| |-- .env (shared base file)
| |-- dev/
| | +-- .env
| |-- staging/
| | +-- .env
| +-- prod/
| +-- .env
|-- etc/
| +-- config.ini
|-- logs/
+-- settings/
|-- __init__.py
+-- settings.py (optional)

Custom settings module

You can create a Python package called settings in your project to define additional configuration derived from NavConfig values.

Inside settings/settings.py:

importsysfromnavconfigimportconfig, DEBUGLOCAL_DEVELOPMENT=DEBUGisTrueandsys.argv[0] =="run.py"SEND_NOTIFICATIONS=config.get("SEND_NOTIFICATIONS", fallback=True)

Variables defined there are accessible through navconfig.conf:

fromnavconfig.confimportLOCAL_DEVELOPMENTifLOCAL_DEVELOPMENT:
print("Running in local development mode.")

Dependencies

  • Python >= 3.10
  • python-dotenv
  • configparser
  • PyYAML
  • pytomlpp
  • orjson
  • cryptography / pycryptodomex
  • hvac (HashiCorp Vault client)

Optional: redis, python-logstash-async, uvloop.

Contribution guidelines

Please see the Contribution Guide for details on:

  • Writing tests
  • Code review process
  • Other guidelines

License

NavConfig is released under the MIT License.

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

NavConfig

NavConfig is a configuration management library for Python projects. It is the default configuration layer of the Navigator Framework, but it works perfectly as a stand-alone tool for any Python application.

NavConfig can load configuration directives from multiple sources (and combine them):

  • Environment files (.env)
  • INI files (via configparser)
  • TOML and YAML files
  • pyproject.toml
  • Redis
  • HashiCorp Vault
  • Python settings modules (settings/settings.py)

The main goal of NavConfig is to centralize configuration access through a single, immutable point of truth that can be shared across modules.

Documentation: https://phenobarbital.github.io/navconfig/

Motivation

Applications require many configuration options. Some of those options hold secrets or credentials and must be kept separate from general settings. Configuration also varies between environments (development, staging, production).

NavConfig addresses this by loading secrets from .env files and structured settings from INI/TOML/YAML files, keeping concerns separated. It also supports retrieving configuration from external stores such as Redis or HashiCorp Vault.

Installation

pip install navconfig

To include optional backends:

# Redis support
pip install navconfig[redis]
# HashiCorp Vault support
pip install navconfig[hvac]
# Logstash logging
pip install navconfig[logstash]
# All features
pip install navconfig[all]

The kardex CLI

NavConfig ships a command line tool called kardex that bootstraps and maintains the configuration layout of a project. It is organised in command groups, each with its own actions:

CommandWhat it does
kardex env createCreate the default project structure.
kardex env create --splitSame, plus the supplementary .env.* files.
kardex env new <name>Add an environment from the shared env/.env.
kardex vault createWrite the HashiCorp Vault directives into env/<env>/.env.
kardex vault migratePush the variables of an environment into Vault.
kardex vault save VAR:VALUEStore one or more variables in Vault.
kardex log enableEnable the [logging] section of etc/config.ini.

Run kardex <group> <action> --help for the full list of options.

Upgrading from 2.x:kardex create and kardex new-env were removed in 3.0. Use kardex env create and kardex env new instead.

Quickstart

1. Create the project structure

kardex env create --env dev

This generates the following structure in the current directory:

.
|-- env/
| |-- .env (shared across environments, no ENV= pin)
| +-- dev/
| +-- .env (pinned to ENV=dev)
|-- etc/
| +-- config.ini
|-- logs/
+-- templates/
  • env/.env -- values shared by every environment. kardex env new uses it as the template for new environments.
  • env/dev/.env -- environment variables (secrets, feature flags, paths).
  • etc/config.ini -- INI-based settings consumed by NavConfig, including a [logging] section.
  • logs/ -- default directory where rotating log files are written.
  • templates/ -- default directory for template files.

Existing files are never overwritten; pass --force when you do want them replaced. Use --path to point at a different project root:

kardex env create --env dev --path /srv/myapp

2. Split the configuration across several files (optional)

NavConfig loads .env first and then a set of supplementary files, which keeps unrelated concerns apart. Pass --split to create them all:

kardex env create --env dev --split
env/dev/
|-- .env base configuration and Vault credentials
|-- .env.resources paths and resource-level directives
|-- .env.databases database connection settings
|-- .env.api HTTP layer settings
|-- .env.cache Redis / cache backend settings
+-- .env.local local overrides, loaded last (keep out of git)

Files are loaded in that order, so .env.local always wins. Everything ends up in the same flat namespace, so config.get("DBHOST") works regardless of which file declared it.

3. Add more environments

kardex env new prod
kardex env new staging --split

This copies env/.env (or the bundled sample if no shared file exists) into env/<name>/.env, adjusting the ENV variable automatically.

4. Select an environment

Set the ENV variable before starting your application:

ENV=prod python app.py

NavConfig loads env/prod/.env and any INI file referenced by its CONFIG_FILE directive.

HashiCorp Vault

Configure the connection

kardex vault create --env dev \
--url http://vault.internal:8200 \
--token "$VAULT_TOKEN" \
--mount-point myapp

This appends a delimited block to env/dev/.env (creating the file if it does not exist yet) with the directives NavConfig reads:

VAULT_ENABLED=true
VAULT_URL=http://vault.internal:8200
VAULT_TOKEN=...
VAULT_MOUNT_POINT=myapp
VAULT_VERSION=2
# VAULT_ENV=

Secrets are then read from <VAULT_MOUNT_POINT>/<ENV>/, and merged on top of the file-based values. Set VAULT_ENV to read from a different path segment than ENV; set NAVCONFIG_FILE_OVERRIDE_ENABLED=true to let the .env files win over Vault instead.

Re-running the command updates only the directives you pass on the command line, which makes token rotation a one-liner. Pass --force to rewrite the whole block.

Migrate an existing .env into Vault

kardex vault migrate --env dev --dry-run # inspect first
kardex vault migrate --env dev

Two families of variables are deliberately left in the .env file, because NavConfig needs them before it can reach Vault:

  • the Vault directives themselves (VAULT_*), and
  • the bootstrap directives (ENV, CONFIG_FILE, SITE_ROOT, ...), which can be included anyway with --include-bootstrap.

Useful options: --include-extra (also migrate the .env.* files), --keep-existing (never overwrite a key already stored in Vault), --file (migrate an arbitrary file) and --yes (skip the confirmation prompt).

Values are masked in the output, and nothing is written until you confirm.

Store single variables

kardex vault save DB_PASSWORD:s3cr3t
kardex vault save "DSN:postgres://user:pass@host:5432/db" API_KEY:abc123

Only the first colon separates the name from the value, so values may contain colons themselves.

Logging

Enable the logging facility with:

kardex log enable

That writes the [logging] section of etc/config.ini (creating the file from the bundled sample when missing) with console and rotating-file output enabled, and creates the log directory. Comments in the INI file are kept.

To also forward records to a Logstash server:

kardex log enable --logstash \
--logstash-host logs.internal \
--logstash-port 5044 \
--logstash-level INFO

The Logstash handler requires pip install navconfig[logstash].

Other options: --loglevel, --logdir, --quiet (no console output), --no-file (no rotating file handler) and --mailer (email alerts on CRITICAL records).

Apply the resulting configuration in your application:

importloggingfromlogging.configimportdictConfigfromnavconfig.loggingimportlogging_configdictConfig(logging_config)
logger=logging.getLogger("MY_APP")
logger.info("Hello World")

Console output uses colored formatting by default:

[INFO] 2024-03-11 19:31:39,408 MY_APP: Hello World

Accessing configuration

fromnavconfigimportconfigAPP_NAME=config.get("APP_NAME")
# "MyApp"

Attribute-style access also works:

APP_NAME=config.APP_NAME

Typed accessors

config.get("APP_NAME") # strconfig.getint("PORT", fallback=8080) # intconfig.getboolean("DEBUG") # boolconfig.getlist("ALLOWED_HOSTS") # list (comma-separated)config.getdict("EXTRA") # dict

An optional fallback argument is returned when the key is not found:

config.get("MISSING_KEY", "default_value")

Initialization

NavConfig resolves the project layout and loads the environment the first time one of its package-level names is accessed (config, BASE_DIR, DEBUG, ENV, ...), not while the package is being imported. Call navconfig.bootstrap() when you need the environment loaded into os.environ as a side effect without touching any of those names:

importnavconfignavconfig.bootstrap()

Configuration directories

By default NavConfig looks for files relative to the project root:

File typeDefault location
.envenv/ (plus ENV subdirectory)
.yml / .tomlenv/
pyproject.tomlproject root
.inietc/

A typical project looks like this:

myapp/
|-- __init__.py
|-- pyproject.toml
|-- env/
| |-- .env (shared base file)
| |-- dev/
| | +-- .env
| |-- staging/
| | +-- .env
| +-- prod/
| +-- .env
|-- etc/
| +-- config.ini
|-- logs/
+-- settings/
|-- __init__.py
+-- settings.py (optional)

Custom settings module

You can create a Python package called settings in your project to define additional configuration derived from NavConfig values.

Inside settings/settings.py:

importsysfromnavconfigimportconfig, DEBUGLOCAL_DEVELOPMENT=DEBUGisTrueandsys.argv[0] =="run.py"SEND_NOTIFICATIONS=config.get("SEND_NOTIFICATIONS", fallback=True)

Variables defined there are accessible through navconfig.conf:

fromnavconfig.confimportLOCAL_DEVELOPMENTifLOCAL_DEVELOPMENT:
print("Running in local development mode.")

Dependencies

  • Python >= 3.10
  • python-dotenv
  • configparser
  • PyYAML
  • pytomlpp
  • orjson
  • cryptography / pycryptodomex
  • hvac (HashiCorp Vault client)

Optional: redis, python-logstash-async, uvloop.

Contribution guidelines

Please see the Contribution Guide for details on:

  • Writing tests
  • Code review process
  • Other guidelines

License

NavConfig is released under the MIT License.

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

NavConfig

NavConfig is a configuration management library for Python projects. It is the default configuration layer of the Navigator Framework, but it works perfectly as a stand-alone tool for any Python application.

NavConfig can load configuration directives from multiple sources (and combine them):

  • Environment files (.env)
  • INI files (via configparser)
  • TOML and YAML files
  • pyproject.toml
  • Redis
  • HashiCorp Vault
  • Python settings modules (settings/settings.py)

The main goal of NavConfig is to centralize configuration access through a single, immutable point of truth that can be shared across modules.

Documentation: https://phenobarbital.github.io/navconfig/

Motivation

Applications require many configuration options. Some of those options hold secrets or credentials and must be kept separate from general settings. Configuration also varies between environments (development, staging, production).

NavConfig addresses this by loading secrets from .env files and structured settings from INI/TOML/YAML files, keeping concerns separated. It also supports retrieving configuration from external stores such as Redis or HashiCorp Vault.

Installation

pip install navconfig

To include optional backends:

# Redis support
pip install navconfig[redis]
# HashiCorp Vault support
pip install navconfig[hvac]
# Logstash logging
pip install navconfig[logstash]
# All features
pip install navconfig[all]

The kardex CLI

NavConfig ships a command line tool called kardex that bootstraps and maintains the configuration layout of a project. It is organised in command groups, each with its own actions:

CommandWhat it does
kardex env createCreate the default project structure.
kardex env create --splitSame, plus the supplementary .env.* files.
kardex env new <name>Add an environment from the shared env/.env.
kardex vault createWrite the HashiCorp Vault directives into env/<env>/.env.
kardex vault migratePush the variables of an environment into Vault.
kardex vault save VAR:VALUEStore one or more variables in Vault.
kardex log enableEnable the [logging] section of etc/config.ini.

Run kardex <group> <action> --help for the full list of options.

Upgrading from 2.x:kardex create and kardex new-env were removed in 3.0. Use kardex env create and kardex env new instead.

Quickstart

1. Create the project structure

kardex env create --env dev

This generates the following structure in the current directory:

.
|-- env/
| |-- .env (shared across environments, no ENV= pin)
| +-- dev/
| +-- .env (pinned to ENV=dev)
|-- etc/
| +-- config.ini
|-- logs/
+-- templates/
  • env/.env -- values shared by every environment. kardex env new uses it as the template for new environments.
  • env/dev/.env -- environment variables (secrets, feature flags, paths).
  • etc/config.ini -- INI-based settings consumed by NavConfig, including a [logging] section.
  • logs/ -- default directory where rotating log files are written.
  • templates/ -- default directory for template files.

Existing files are never overwritten; pass --force when you do want them replaced. Use --path to point at a different project root:

kardex env create --env dev --path /srv/myapp

2. Split the configuration across several files (optional)

NavConfig loads .env first and then a set of supplementary files, which keeps unrelated concerns apart. Pass --split to create them all:

kardex env create --env dev --split
env/dev/
|-- .env base configuration and Vault credentials
|-- .env.resources paths and resource-level directives
|-- .env.databases database connection settings
|-- .env.api HTTP layer settings
|-- .env.cache Redis / cache backend settings
+-- .env.local local overrides, loaded last (keep out of git)

Files are loaded in that order, so .env.local always wins. Everything ends up in the same flat namespace, so config.get("DBHOST") works regardless of which file declared it.

3. Add more environments

kardex env new prod
kardex env new staging --split

This copies env/.env (or the bundled sample if no shared file exists) into env/<name>/.env, adjusting the ENV variable automatically.

4. Select an environment

Set the ENV variable before starting your application:

ENV=prod python app.py

NavConfig loads env/prod/.env and any INI file referenced by its CONFIG_FILE directive.

HashiCorp Vault

Configure the connection

kardex vault create --env dev \
--url http://vault.internal:8200 \
--token "$VAULT_TOKEN" \
--mount-point myapp

This appends a delimited block to env/dev/.env (creating the file if it does not exist yet) with the directives NavConfig reads:

VAULT_ENABLED=true
VAULT_URL=http://vault.internal:8200
VAULT_TOKEN=...
VAULT_MOUNT_POINT=myapp
VAULT_VERSION=2
# VAULT_ENV=

Secrets are then read from <VAULT_MOUNT_POINT>/<ENV>/, and merged on top of the file-based values. Set VAULT_ENV to read from a different path segment than ENV; set NAVCONFIG_FILE_OVERRIDE_ENABLED=true to let the .env files win over Vault instead.

Re-running the command updates only the directives you pass on the command line, which makes token rotation a one-liner. Pass --force to rewrite the whole block.

Migrate an existing .env into Vault

kardex vault migrate --env dev --dry-run # inspect first
kardex vault migrate --env dev

Two families of variables are deliberately left in the .env file, because NavConfig needs them before it can reach Vault:

  • the Vault directives themselves (VAULT_*), and
  • the bootstrap directives (ENV, CONFIG_FILE, SITE_ROOT, ...), which can be included anyway with --include-bootstrap.

Useful options: --include-extra (also migrate the .env.* files), --keep-existing (never overwrite a key already stored in Vault), --file (migrate an arbitrary file) and --yes (skip the confirmation prompt).

Values are masked in the output, and nothing is written until you confirm.

Store single variables

kardex vault save DB_PASSWORD:s3cr3t
kardex vault save "DSN:postgres://user:pass@host:5432/db" API_KEY:abc123

Only the first colon separates the name from the value, so values may contain colons themselves.

Logging

Enable the logging facility with:

kardex log enable

That writes the [logging] section of etc/config.ini (creating the file from the bundled sample when missing) with console and rotating-file output enabled, and creates the log directory. Comments in the INI file are kept.

To also forward records to a Logstash server:

kardex log enable --logstash \
--logstash-host logs.internal \
--logstash-port 5044 \
--logstash-level INFO

The Logstash handler requires pip install navconfig[logstash].

Other options: --loglevel, --logdir, --quiet (no console output), --no-file (no rotating file handler) and --mailer (email alerts on CRITICAL records).

Apply the resulting configuration in your application:

importloggingfromlogging.configimportdictConfigfromnavconfig.loggingimportlogging_configdictConfig(logging_config)
logger=logging.getLogger("MY_APP")
logger.info("Hello World")

Console output uses colored formatting by default:

[INFO] 2024-03-11 19:31:39,408 MY_APP: Hello World

Accessing configuration

fromnavconfigimportconfigAPP_NAME=config.get("APP_NAME")
# "MyApp"

Attribute-style access also works:

APP_NAME=config.APP_NAME

Typed accessors

config.get("APP_NAME") # strconfig.getint("PORT", fallback=8080) # intconfig.getboolean("DEBUG") # boolconfig.getlist("ALLOWED_HOSTS") # list (comma-separated)config.getdict("EXTRA") # dict

An optional fallback argument is returned when the key is not found:

config.get("MISSING_KEY", "default_value")

Initialization

NavConfig resolves the project layout and loads the environment the first time one of its package-level names is accessed (config, BASE_DIR, DEBUG, ENV, ...), not while the package is being imported. Call navconfig.bootstrap() when you need the environment loaded into os.environ as a side effect without touching any of those names:

importnavconfignavconfig.bootstrap()

Configuration directories

By default NavConfig looks for files relative to the project root:

File typeDefault location
.envenv/ (plus ENV subdirectory)
.yml / .tomlenv/
pyproject.tomlproject root
.inietc/

A typical project looks like this:

myapp/
|-- __init__.py
|-- pyproject.toml
|-- env/
| |-- .env (shared base file)
| |-- dev/
| | +-- .env
| |-- staging/
| | +-- .env
| +-- prod/
| +-- .env
|-- etc/
| +-- config.ini
|-- logs/
+-- settings/
|-- __init__.py
+-- settings.py (optional)

Custom settings module

You can create a Python package called settings in your project to define additional configuration derived from NavConfig values.

Inside settings/settings.py:

importsysfromnavconfigimportconfig, DEBUGLOCAL_DEVELOPMENT=DEBUGisTrueandsys.argv[0] =="run.py"SEND_NOTIFICATIONS=config.get("SEND_NOTIFICATIONS", fallback=True)

Variables defined there are accessible through navconfig.conf:

fromnavconfig.confimportLOCAL_DEVELOPMENTifLOCAL_DEVELOPMENT:
print("Running in local development mode.")

Dependencies

  • Python >= 3.10
  • python-dotenv
  • configparser
  • PyYAML
  • pytomlpp
  • orjson
  • cryptography / pycryptodomex
  • hvac (HashiCorp Vault client)

Optional: redis, python-logstash-async, uvloop.

Contribution guidelines

Please see the Contribution Guide for details on:

  • Writing tests
  • Code review process
  • Other guidelines

License

NavConfig is released under the MIT License.

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

NavConfig

NavConfig is a configuration management library for Python projects. It is the default configuration layer of the Navigator Framework, but it works perfectly as a stand-alone tool for any Python application.

NavConfig can load configuration directives from multiple sources (and combine them):

  • Environment files (.env)
  • INI files (via configparser)
  • TOML and YAML files
  • pyproject.toml
  • Redis
  • HashiCorp Vault
  • Python settings modules (settings/settings.py)

The main goal of NavConfig is to centralize configuration access through a single, immutable point of truth that can be shared across modules.

Documentation: https://phenobarbital.github.io/navconfig/

Motivation

Applications require many configuration options. Some of those options hold secrets or credentials and must be kept separate from general settings. Configuration also varies between environments (development, staging, production).

NavConfig addresses this by loading secrets from .env files and structured settings from INI/TOML/YAML files, keeping concerns separated. It also supports retrieving configuration from external stores such as Redis or HashiCorp Vault.

Installation

pip install navconfig

To include optional backends:

# Redis support
pip install navconfig[redis]
# HashiCorp Vault support
pip install navconfig[hvac]
# Logstash logging
pip install navconfig[logstash]
# All features
pip install navconfig[all]

The kardex CLI

NavConfig ships a command line tool called kardex that bootstraps and maintains the configuration layout of a project. It is organised in command groups, each with its own actions:

CommandWhat it does
kardex env createCreate the default project structure.
kardex env create --splitSame, plus the supplementary .env.* files.
kardex env new <name>Add an environment from the shared env/.env.
kardex vault createWrite the HashiCorp Vault directives into env/<env>/.env.
kardex vault migratePush the variables of an environment into Vault.
kardex vault save VAR:VALUEStore one or more variables in Vault.
kardex log enableEnable the [logging] section of etc/config.ini.

Run kardex <group> <action> --help for the full list of options.

Upgrading from 2.x:kardex create and kardex new-env were removed in 3.0. Use kardex env create and kardex env new instead.

Quickstart

1. Create the project structure

kardex env create --env dev

This generates the following structure in the current directory:

.
|-- env/
| |-- .env (shared across environments, no ENV= pin)
| +-- dev/
| +-- .env (pinned to ENV=dev)
|-- etc/
| +-- config.ini
|-- logs/
+-- templates/
  • env/.env -- values shared by every environment. kardex env new uses it as the template for new environments.
  • env/dev/.env -- environment variables (secrets, feature flags, paths).
  • etc/config.ini -- INI-based settings consumed by NavConfig, including a [logging] section.
  • logs/ -- default directory where rotating log files are written.
  • templates/ -- default directory for template files.

Existing files are never overwritten; pass --force when you do want them replaced. Use --path to point at a different project root:

kardex env create --env dev --path /srv/myapp

2. Split the configuration across several files (optional)

NavConfig loads .env first and then a set of supplementary files, which keeps unrelated concerns apart. Pass --split to create them all:

kardex env create --env dev --split
env/dev/
|-- .env base configuration and Vault credentials
|-- .env.resources paths and resource-level directives
|-- .env.databases database connection settings
|-- .env.api HTTP layer settings
|-- .env.cache Redis / cache backend settings
+-- .env.local local overrides, loaded last (keep out of git)

Files are loaded in that order, so .env.local always wins. Everything ends up in the same flat namespace, so config.get("DBHOST") works regardless of which file declared it.

3. Add more environments

kardex env new prod
kardex env new staging --split

This copies env/.env (or the bundled sample if no shared file exists) into env/<name>/.env, adjusting the ENV variable automatically.

4. Select an environment

Set the ENV variable before starting your application:

ENV=prod python app.py

NavConfig loads env/prod/.env and any INI file referenced by its CONFIG_FILE directive.

HashiCorp Vault

Configure the connection

kardex vault create --env dev \
--url http://vault.internal:8200 \
--token "$VAULT_TOKEN" \
--mount-point myapp

This appends a delimited block to env/dev/.env (creating the file if it does not exist yet) with the directives NavConfig reads:

VAULT_ENABLED=true
VAULT_URL=http://vault.internal:8200
VAULT_TOKEN=...
VAULT_MOUNT_POINT=myapp
VAULT_VERSION=2
# VAULT_ENV=

Secrets are then read from <VAULT_MOUNT_POINT>/<ENV>/, and merged on top of the file-based values. Set VAULT_ENV to read from a different path segment than ENV; set NAVCONFIG_FILE_OVERRIDE_ENABLED=true to let the .env files win over Vault instead.

Re-running the command updates only the directives you pass on the command line, which makes token rotation a one-liner. Pass --force to rewrite the whole block.

Migrate an existing .env into Vault

kardex vault migrate --env dev --dry-run # inspect first
kardex vault migrate --env dev

Two families of variables are deliberately left in the .env file, because NavConfig needs them before it can reach Vault:

  • the Vault directives themselves (VAULT_*), and
  • the bootstrap directives (ENV, CONFIG_FILE, SITE_ROOT, ...), which can be included anyway with --include-bootstrap.

Useful options: --include-extra (also migrate the .env.* files), --keep-existing (never overwrite a key already stored in Vault), --file (migrate an arbitrary file) and --yes (skip the confirmation prompt).

Values are masked in the output, and nothing is written until you confirm.

Store single variables

kardex vault save DB_PASSWORD:s3cr3t
kardex vault save "DSN:postgres://user:pass@host:5432/db" API_KEY:abc123

Only the first colon separates the name from the value, so values may contain colons themselves.

Logging

Enable the logging facility with:

kardex log enable

That writes the [logging] section of etc/config.ini (creating the file from the bundled sample when missing) with console and rotating-file output enabled, and creates the log directory. Comments in the INI file are kept.

To also forward records to a Logstash server:

kardex log enable --logstash \
--logstash-host logs.internal \
--logstash-port 5044 \
--logstash-level INFO

The Logstash handler requires pip install navconfig[logstash].

Other options: --loglevel, --logdir, --quiet (no console output), --no-file (no rotating file handler) and --mailer (email alerts on CRITICAL records).

Apply the resulting configuration in your application:

importloggingfromlogging.configimportdictConfigfromnavconfig.loggingimportlogging_configdictConfig(logging_config)
logger=logging.getLogger("MY_APP")
logger.info("Hello World")

Console output uses colored formatting by default:

[INFO] 2024-03-11 19:31:39,408 MY_APP: Hello World

Accessing configuration

fromnavconfigimportconfigAPP_NAME=config.get("APP_NAME")
# "MyApp"

Attribute-style access also works:

APP_NAME=config.APP_NAME

Typed accessors

config.get("APP_NAME") # strconfig.getint("PORT", fallback=8080) # intconfig.getboolean("DEBUG") # boolconfig.getlist("ALLOWED_HOSTS") # list (comma-separated)config.getdict("EXTRA") # dict

An optional fallback argument is returned when the key is not found:

config.get("MISSING_KEY", "default_value")

Initialization

NavConfig resolves the project layout and loads the environment the first time one of its package-level names is accessed (config, BASE_DIR, DEBUG, ENV, ...), not while the package is being imported. Call navconfig.bootstrap() when you need the environment loaded into os.environ as a side effect without touching any of those names:

importnavconfignavconfig.bootstrap()

Configuration directories

By default NavConfig looks for files relative to the project root:

File typeDefault location
.envenv/ (plus ENV subdirectory)
.yml / .tomlenv/
pyproject.tomlproject root
.inietc/

A typical project looks like this:

myapp/
|-- __init__.py
|-- pyproject.toml
|-- env/
| |-- .env (shared base file)
| |-- dev/
| | +-- .env
| |-- staging/
| | +-- .env
| +-- prod/
| +-- .env
|-- etc/
| +-- config.ini
|-- logs/
+-- settings/
|-- __init__.py
+-- settings.py (optional)

Custom settings module

You can create a Python package called settings in your project to define additional configuration derived from NavConfig values.

Inside settings/settings.py:

importsysfromnavconfigimportconfig, DEBUGLOCAL_DEVELOPMENT=DEBUGisTrueandsys.argv[0] =="run.py"SEND_NOTIFICATIONS=config.get("SEND_NOTIFICATIONS", fallback=True)

Variables defined there are accessible through navconfig.conf:

fromnavconfig.confimportLOCAL_DEVELOPMENTifLOCAL_DEVELOPMENT:
print("Running in local development mode.")

Dependencies

  • Python >= 3.10
  • python-dotenv
  • configparser
  • PyYAML
  • pytomlpp
  • orjson
  • cryptography / pycryptodomex
  • hvac (HashiCorp Vault client)

Optional: redis, python-logstash-async, uvloop.

Contribution guidelines

Please see the Contribution Guide for details on:

  • Writing tests
  • Code review process
  • Other guidelines

License

NavConfig is released under the MIT License.

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

NavConfig

NavConfig is a configuration management library for Python projects. It is the default configuration layer of the Navigator Framework, but it works perfectly as a stand-alone tool for any Python application.

NavConfig can load configuration directives from multiple sources (and combine them):

  • Environment files (.env)
  • INI files (via configparser)
  • TOML and YAML files
  • pyproject.toml
  • Redis
  • HashiCorp Vault
  • Python settings modules (settings/settings.py)

The main goal of NavConfig is to centralize configuration access through a single, immutable point of truth that can be shared across modules.

Documentation: https://phenobarbital.github.io/navconfig/

Motivation

Applications require many configuration options. Some of those options hold secrets or credentials and must be kept separate from general settings. Configuration also varies between environments (development, staging, production).

NavConfig addresses this by loading secrets from .env files and structured settings from INI/TOML/YAML files, keeping concerns separated. It also supports retrieving configuration from external stores such as Redis or HashiCorp Vault.

Installation

pip install navconfig

To include optional backends:

# Redis support
pip install navconfig[redis]
# HashiCorp Vault support
pip install navconfig[hvac]
# Logstash logging
pip install navconfig[logstash]
# All features
pip install navconfig[all]

The kardex CLI

NavConfig ships a command line tool called kardex that bootstraps and maintains the configuration layout of a project. It is organised in command groups, each with its own actions:

CommandWhat it does
kardex env createCreate the default project structure.
kardex env create --splitSame, plus the supplementary .env.* files.
kardex env new <name>Add an environment from the shared env/.env.
kardex vault createWrite the HashiCorp Vault directives into env/<env>/.env.
kardex vault migratePush the variables of an environment into Vault.
kardex vault save VAR:VALUEStore one or more variables in Vault.
kardex log enableEnable the [logging] section of etc/config.ini.

Run kardex <group> <action> --help for the full list of options.

Upgrading from 2.x:kardex create and kardex new-env were removed in 3.0. Use kardex env create and kardex env new instead.

Quickstart

1. Create the project structure

kardex env create --env dev

This generates the following structure in the current directory:

.
|-- env/
| |-- .env (shared across environments, no ENV= pin)
| +-- dev/
| +-- .env (pinned to ENV=dev)
|-- etc/
| +-- config.ini
|-- logs/
+-- templates/
  • env/.env -- values shared by every environment. kardex env new uses it as the template for new environments.
  • env/dev/.env -- environment variables (secrets, feature flags, paths).
  • etc/config.ini -- INI-based settings consumed by NavConfig, including a [logging] section.
  • logs/ -- default directory where rotating log files are written.
  • templates/ -- default directory for template files.

Existing files are never overwritten; pass --force when you do want them replaced. Use --path to point at a different project root:

kardex env create --env dev --path /srv/myapp

2. Split the configuration across several files (optional)

NavConfig loads .env first and then a set of supplementary files, which keeps unrelated concerns apart. Pass --split to create them all:

kardex env create --env dev --split
env/dev/
|-- .env base configuration and Vault credentials
|-- .env.resources paths and resource-level directives
|-- .env.databases database connection settings
|-- .env.api HTTP layer settings
|-- .env.cache Redis / cache backend settings
+-- .env.local local overrides, loaded last (keep out of git)

Files are loaded in that order, so .env.local always wins. Everything ends up in the same flat namespace, so config.get("DBHOST") works regardless of which file declared it.

3. Add more environments

kardex env new prod
kardex env new staging --split

This copies env/.env (or the bundled sample if no shared file exists) into env/<name>/.env, adjusting the ENV variable automatically.

4. Select an environment

Set the ENV variable before starting your application:

ENV=prod python app.py

NavConfig loads env/prod/.env and any INI file referenced by its CONFIG_FILE directive.

HashiCorp Vault

Configure the connection

kardex vault create --env dev \
--url http://vault.internal:8200 \
--token "$VAULT_TOKEN" \
--mount-point myapp

This appends a delimited block to env/dev/.env (creating the file if it does not exist yet) with the directives NavConfig reads:

VAULT_ENABLED=true
VAULT_URL=http://vault.internal:8200
VAULT_TOKEN=...
VAULT_MOUNT_POINT=myapp
VAULT_VERSION=2
# VAULT_ENV=

Secrets are then read from <VAULT_MOUNT_POINT>/<ENV>/, and merged on top of the file-based values. Set VAULT_ENV to read from a different path segment than ENV; set NAVCONFIG_FILE_OVERRIDE_ENABLED=true to let the .env files win over Vault instead.

Re-running the command updates only the directives you pass on the command line, which makes token rotation a one-liner. Pass --force to rewrite the whole block.

Migrate an existing .env into Vault

kardex vault migrate --env dev --dry-run # inspect first
kardex vault migrate --env dev

Two families of variables are deliberately left in the .env file, because NavConfig needs them before it can reach Vault:

  • the Vault directives themselves (VAULT_*), and
  • the bootstrap directives (ENV, CONFIG_FILE, SITE_ROOT, ...), which can be included anyway with --include-bootstrap.

Useful options: --include-extra (also migrate the .env.* files), --keep-existing (never overwrite a key already stored in Vault), --file (migrate an arbitrary file) and --yes (skip the confirmation prompt).

Values are masked in the output, and nothing is written until you confirm.

Store single variables

kardex vault save DB_PASSWORD:s3cr3t
kardex vault save "DSN:postgres://user:pass@host:5432/db" API_KEY:abc123

Only the first colon separates the name from the value, so values may contain colons themselves.

Logging

Enable the logging facility with:

kardex log enable

That writes the [logging] section of etc/config.ini (creating the file from the bundled sample when missing) with console and rotating-file output enabled, and creates the log directory. Comments in the INI file are kept.

To also forward records to a Logstash server:

kardex log enable --logstash \
--logstash-host logs.internal \
--logstash-port 5044 \
--logstash-level INFO

The Logstash handler requires pip install navconfig[logstash].

Other options: --loglevel, --logdir, --quiet (no console output), --no-file (no rotating file handler) and --mailer (email alerts on CRITICAL records).

Apply the resulting configuration in your application:

importloggingfromlogging.configimportdictConfigfromnavconfig.loggingimportlogging_configdictConfig(logging_config)
logger=logging.getLogger("MY_APP")
logger.info("Hello World")

Console output uses colored formatting by default:

[INFO] 2024-03-11 19:31:39,408 MY_APP: Hello World

Accessing configuration

fromnavconfigimportconfigAPP_NAME=config.get("APP_NAME")
# "MyApp"

Attribute-style access also works:

APP_NAME=config.APP_NAME

Typed accessors

config.get("APP_NAME") # strconfig.getint("PORT", fallback=8080) # intconfig.getboolean("DEBUG") # boolconfig.getlist("ALLOWED_HOSTS") # list (comma-separated)config.getdict("EXTRA") # dict

An optional fallback argument is returned when the key is not found:

config.get("MISSING_KEY", "default_value")

Initialization

NavConfig resolves the project layout and loads the environment the first time one of its package-level names is accessed (config, BASE_DIR, DEBUG, ENV, ...), not while the package is being imported. Call navconfig.bootstrap() when you need the environment loaded into os.environ as a side effect without touching any of those names:

importnavconfignavconfig.bootstrap()

Configuration directories

By default NavConfig looks for files relative to the project root:

File typeDefault location
.envenv/ (plus ENV subdirectory)
.yml / .tomlenv/
pyproject.tomlproject root
.inietc/

A typical project looks like this:

myapp/
|-- __init__.py
|-- pyproject.toml
|-- env/
| |-- .env (shared base file)
| |-- dev/
| | +-- .env
| |-- staging/
| | +-- .env
| +-- prod/
| +-- .env
|-- etc/
| +-- config.ini
|-- logs/
+-- settings/
|-- __init__.py
+-- settings.py (optional)

Custom settings module

You can create a Python package called settings in your project to define additional configuration derived from NavConfig values.

Inside settings/settings.py:

importsysfromnavconfigimportconfig, DEBUGLOCAL_DEVELOPMENT=DEBUGisTrueandsys.argv[0] =="run.py"SEND_NOTIFICATIONS=config.get("SEND_NOTIFICATIONS", fallback=True)

Variables defined there are accessible through navconfig.conf:

fromnavconfig.confimportLOCAL_DEVELOPMENTifLOCAL_DEVELOPMENT:
print("Running in local development mode.")

Dependencies

  • Python >= 3.10
  • python-dotenv
  • configparser
  • PyYAML
  • pytomlpp
  • orjson
  • cryptography / pycryptodomex
  • hvac (HashiCorp Vault client)

Optional: redis, python-logstash-async, uvloop.

Contribution guidelines

Please see the Contribution Guide for details on:

  • Writing tests
  • Code review process
  • Other guidelines

License

NavConfig is released under the MIT License.

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

NavConfig

NavConfig is a configuration management library for Python projects. It is the default configuration layer of the Navigator Framework, but it works perfectly as a stand-alone tool for any Python application.

NavConfig can load configuration directives from multiple sources (and combine them):

  • Environment files (.env)
  • INI files (via configparser)
  • TOML and YAML files
  • pyproject.toml
  • Redis
  • HashiCorp Vault
  • Python settings modules (settings/settings.py)

The main goal of NavConfig is to centralize configuration access through a single, immutable point of truth that can be shared across modules.

Documentation: https://phenobarbital.github.io/navconfig/

Motivation

Applications require many configuration options. Some of those options hold secrets or credentials and must be kept separate from general settings. Configuration also varies between environments (development, staging, production).

NavConfig addresses this by loading secrets from .env files and structured settings from INI/TOML/YAML files, keeping concerns separated. It also supports retrieving configuration from external stores such as Redis or HashiCorp Vault.

Installation

pip install navconfig

To include optional backends:

# Redis support
pip install navconfig[redis]
# HashiCorp Vault support
pip install navconfig[hvac]
# Logstash logging
pip install navconfig[logstash]
# All features
pip install navconfig[all]

The kardex CLI

NavConfig ships a command line tool called kardex that bootstraps and maintains the configuration layout of a project. It is organised in command groups, each with its own actions:

CommandWhat it does
kardex env createCreate the default project structure.
kardex env create --splitSame, plus the supplementary .env.* files.
kardex env new <name>Add an environment from the shared env/.env.
kardex vault createWrite the HashiCorp Vault directives into env/<env>/.env.
kardex vault migratePush the variables of an environment into Vault.
kardex vault save VAR:VALUEStore one or more variables in Vault.
kardex log enableEnable the [logging] section of etc/config.ini.

Run kardex <group> <action> --help for the full list of options.

Upgrading from 2.x:kardex create and kardex new-env were removed in 3.0. Use kardex env create and kardex env new instead.

Quickstart

1. Create the project structure

kardex env create --env dev

This generates the following structure in the current directory:

.
|-- env/
| |-- .env (shared across environments, no ENV= pin)
| +-- dev/
| +-- .env (pinned to ENV=dev)
|-- etc/
| +-- config.ini
|-- logs/
+-- templates/
  • env/.env -- values shared by every environment. kardex env new uses it as the template for new environments.
  • env/dev/.env -- environment variables (secrets, feature flags, paths).
  • etc/config.ini -- INI-based settings consumed by NavConfig, including a [logging] section.
  • logs/ -- default directory where rotating log files are written.
  • templates/ -- default directory for template files.

Existing files are never overwritten; pass --force when you do want them replaced. Use --path to point at a different project root:

kardex env create --env dev --path /srv/myapp

2. Split the configuration across several files (optional)

NavConfig loads .env first and then a set of supplementary files, which keeps unrelated concerns apart. Pass --split to create them all:

kardex env create --env dev --split
env/dev/
|-- .env base configuration and Vault credentials
|-- .env.resources paths and resource-level directives
|-- .env.databases database connection settings
|-- .env.api HTTP layer settings
|-- .env.cache Redis / cache backend settings
+-- .env.local local overrides, loaded last (keep out of git)

Files are loaded in that order, so .env.local always wins. Everything ends up in the same flat namespace, so config.get("DBHOST") works regardless of which file declared it.

3. Add more environments

kardex env new prod
kardex env new staging --split

This copies env/.env (or the bundled sample if no shared file exists) into env/<name>/.env, adjusting the ENV variable automatically.

4. Select an environment

Set the ENV variable before starting your application:

ENV=prod python app.py

NavConfig loads env/prod/.env and any INI file referenced by its CONFIG_FILE directive.

HashiCorp Vault

Configure the connection

kardex vault create --env dev \
--url http://vault.internal:8200 \
--token "$VAULT_TOKEN" \
--mount-point myapp

This appends a delimited block to env/dev/.env (creating the file if it does not exist yet) with the directives NavConfig reads:

VAULT_ENABLED=true
VAULT_URL=http://vault.internal:8200
VAULT_TOKEN=...
VAULT_MOUNT_POINT=myapp
VAULT_VERSION=2
# VAULT_ENV=

Secrets are then read from <VAULT_MOUNT_POINT>/<ENV>/, and merged on top of the file-based values. Set VAULT_ENV to read from a different path segment than ENV; set NAVCONFIG_FILE_OVERRIDE_ENABLED=true to let the .env files win over Vault instead.

Re-running the command updates only the directives you pass on the command line, which makes token rotation a one-liner. Pass --force to rewrite the whole block.

Migrate an existing .env into Vault

kardex vault migrate --env dev --dry-run # inspect first
kardex vault migrate --env dev

Two families of variables are deliberately left in the .env file, because NavConfig needs them before it can reach Vault:

  • the Vault directives themselves (VAULT_*), and
  • the bootstrap directives (ENV, CONFIG_FILE, SITE_ROOT, ...), which can be included anyway with --include-bootstrap.

Useful options: --include-extra (also migrate the .env.* files), --keep-existing (never overwrite a key already stored in Vault), --file (migrate an arbitrary file) and --yes (skip the confirmation prompt).

Values are masked in the output, and nothing is written until you confirm.

Store single variables

kardex vault save DB_PASSWORD:s3cr3t
kardex vault save "DSN:postgres://user:pass@host:5432/db" API_KEY:abc123

Only the first colon separates the name from the value, so values may contain colons themselves.

Logging

Enable the logging facility with:

kardex log enable

That writes the [logging] section of etc/config.ini (creating the file from the bundled sample when missing) with console and rotating-file output enabled, and creates the log directory. Comments in the INI file are kept.

To also forward records to a Logstash server:

kardex log enable --logstash \
--logstash-host logs.internal \
--logstash-port 5044 \
--logstash-level INFO

The Logstash handler requires pip install navconfig[logstash].

Other options: --loglevel, --logdir, --quiet (no console output), --no-file (no rotating file handler) and --mailer (email alerts on CRITICAL records).

Apply the resulting configuration in your application:

importloggingfromlogging.configimportdictConfigfromnavconfig.loggingimportlogging_configdictConfig(logging_config)
logger=logging.getLogger("MY_APP")
logger.info("Hello World")

Console output uses colored formatting by default:

[INFO] 2024-03-11 19:31:39,408 MY_APP: Hello World

Accessing configuration

fromnavconfigimportconfigAPP_NAME=config.get("APP_NAME")
# "MyApp"

Attribute-style access also works:

APP_NAME=config.APP_NAME

Typed accessors

config.get("APP_NAME") # strconfig.getint("PORT", fallback=8080) # intconfig.getboolean("DEBUG") # boolconfig.getlist("ALLOWED_HOSTS") # list (comma-separated)config.getdict("EXTRA") # dict

An optional fallback argument is returned when the key is not found:

config.get("MISSING_KEY", "default_value")

Initialization

NavConfig resolves the project layout and loads the environment the first time one of its package-level names is accessed (config, BASE_DIR, DEBUG, ENV, ...), not while the package is being imported. Call navconfig.bootstrap() when you need the environment loaded into os.environ as a side effect without touching any of those names:

importnavconfignavconfig.bootstrap()

Configuration directories

By default NavConfig looks for files relative to the project root:

File typeDefault location
.envenv/ (plus ENV subdirectory)
.yml / .tomlenv/
pyproject.tomlproject root
.inietc/

A typical project looks like this:

myapp/
|-- __init__.py
|-- pyproject.toml
|-- env/
| |-- .env (shared base file)
| |-- dev/
| | +-- .env
| |-- staging/
| | +-- .env
| +-- prod/
| +-- .env
|-- etc/
| +-- config.ini
|-- logs/
+-- settings/
|-- __init__.py
+-- settings.py (optional)

Custom settings module

You can create a Python package called settings in your project to define additional configuration derived from NavConfig values.

Inside settings/settings.py:

importsysfromnavconfigimportconfig, DEBUGLOCAL_DEVELOPMENT=DEBUGisTrueandsys.argv[0] =="run.py"SEND_NOTIFICATIONS=config.get("SEND_NOTIFICATIONS", fallback=True)

Variables defined there are accessible through navconfig.conf:

fromnavconfig.confimportLOCAL_DEVELOPMENTifLOCAL_DEVELOPMENT:
print("Running in local development mode.")

Dependencies

  • Python >= 3.10
  • python-dotenv
  • configparser
  • PyYAML
  • pytomlpp
  • orjson
  • cryptography / pycryptodomex
  • hvac (HashiCorp Vault client)

Optional: redis, python-logstash-async, uvloop.

Contribution guidelines

Please see the Contribution Guide for details on:

  • Writing tests
  • Code review process
  • Other guidelines

License

NavConfig is released under the MIT License.

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

NavConfig

NavConfig is a configuration management library for Python projects. It is the default configuration layer of the Navigator Framework, but it works perfectly as a stand-alone tool for any Python application.

NavConfig can load configuration directives from multiple sources (and combine them):

  • Environment files (.env)
  • INI files (via configparser)
  • TOML and YAML files
  • pyproject.toml
  • Redis
  • HashiCorp Vault
  • Python settings modules (settings/settings.py)

The main goal of NavConfig is to centralize configuration access through a single, immutable point of truth that can be shared across modules.

Documentation: https://phenobarbital.github.io/navconfig/

Motivation

Applications require many configuration options. Some of those options hold secrets or credentials and must be kept separate from general settings. Configuration also varies between environments (development, staging, production).

NavConfig addresses this by loading secrets from .env files and structured settings from INI/TOML/YAML files, keeping concerns separated. It also supports retrieving configuration from external stores such as Redis or HashiCorp Vault.

Installation

pip install navconfig

To include optional backends:

# Redis support
pip install navconfig[redis]
# HashiCorp Vault support
pip install navconfig[hvac]
# Logstash logging
pip install navconfig[logstash]
# All features
pip install navconfig[all]

The kardex CLI

NavConfig ships a command line tool called kardex that bootstraps and maintains the configuration layout of a project. It is organised in command groups, each with its own actions:

CommandWhat it does
kardex env createCreate the default project structure.
kardex env create --splitSame, plus the supplementary .env.* files.
kardex env new <name>Add an environment from the shared env/.env.
kardex vault createWrite the HashiCorp Vault directives into env/<env>/.env.
kardex vault migratePush the variables of an environment into Vault.
kardex vault save VAR:VALUEStore one or more variables in Vault.
kardex log enableEnable the [logging] section of etc/config.ini.

Run kardex <group> <action> --help for the full list of options.

Upgrading from 2.x:kardex create and kardex new-env were removed in 3.0. Use kardex env create and kardex env new instead.

Quickstart

1. Create the project structure

kardex env create --env dev

This generates the following structure in the current directory:

.
|-- env/
| |-- .env (shared across environments, no ENV= pin)
| +-- dev/
| +-- .env (pinned to ENV=dev)
|-- etc/
| +-- config.ini
|-- logs/
+-- templates/
  • env/.env -- values shared by every environment. kardex env new uses it as the template for new environments.
  • env/dev/.env -- environment variables (secrets, feature flags, paths).
  • etc/config.ini -- INI-based settings consumed by NavConfig, including a [logging] section.
  • logs/ -- default directory where rotating log files are written.
  • templates/ -- default directory for template files.

Existing files are never overwritten; pass --force when you do want them replaced. Use --path to point at a different project root:

kardex env create --env dev --path /srv/myapp

2. Split the configuration across several files (optional)

NavConfig loads .env first and then a set of supplementary files, which keeps unrelated concerns apart. Pass --split to create them all:

kardex env create --env dev --split
env/dev/
|-- .env base configuration and Vault credentials
|-- .env.resources paths and resource-level directives
|-- .env.databases database connection settings
|-- .env.api HTTP layer settings
|-- .env.cache Redis / cache backend settings
+-- .env.local local overrides, loaded last (keep out of git)

Files are loaded in that order, so .env.local always wins. Everything ends up in the same flat namespace, so config.get("DBHOST") works regardless of which file declared it.

3. Add more environments

kardex env new prod
kardex env new staging --split

This copies env/.env (or the bundled sample if no shared file exists) into env/<name>/.env, adjusting the ENV variable automatically.

4. Select an environment

Set the ENV variable before starting your application:

ENV=prod python app.py

NavConfig loads env/prod/.env and any INI file referenced by its CONFIG_FILE directive.

HashiCorp Vault

Configure the connection

kardex vault create --env dev \
--url http://vault.internal:8200 \
--token "$VAULT_TOKEN" \
--mount-point myapp

This appends a delimited block to env/dev/.env (creating the file if it does not exist yet) with the directives NavConfig reads:

VAULT_ENABLED=true
VAULT_URL=http://vault.internal:8200
VAULT_TOKEN=...
VAULT_MOUNT_POINT=myapp
VAULT_VERSION=2
# VAULT_ENV=

Secrets are then read from <VAULT_MOUNT_POINT>/<ENV>/, and merged on top of the file-based values. Set VAULT_ENV to read from a different path segment than ENV; set NAVCONFIG_FILE_OVERRIDE_ENABLED=true to let the .env files win over Vault instead.

Re-running the command updates only the directives you pass on the command line, which makes token rotation a one-liner. Pass --force to rewrite the whole block.

Migrate an existing .env into Vault

kardex vault migrate --env dev --dry-run # inspect first
kardex vault migrate --env dev

Two families of variables are deliberately left in the .env file, because NavConfig needs them before it can reach Vault:

  • the Vault directives themselves (VAULT_*), and
  • the bootstrap directives (ENV, CONFIG_FILE, SITE_ROOT, ...), which can be included anyway with --include-bootstrap.

Useful options: --include-extra (also migrate the .env.* files), --keep-existing (never overwrite a key already stored in Vault), --file (migrate an arbitrary file) and --yes (skip the confirmation prompt).

Values are masked in the output, and nothing is written until you confirm.

Store single variables

kardex vault save DB_PASSWORD:s3cr3t
kardex vault save "DSN:postgres://user:pass@host:5432/db" API_KEY:abc123

Only the first colon separates the name from the value, so values may contain colons themselves.

Logging

Enable the logging facility with:

kardex log enable

That writes the [logging] section of etc/config.ini (creating the file from the bundled sample when missing) with console and rotating-file output enabled, and creates the log directory. Comments in the INI file are kept.

To also forward records to a Logstash server:

kardex log enable --logstash \
--logstash-host logs.internal \
--logstash-port 5044 \
--logstash-level INFO

The Logstash handler requires pip install navconfig[logstash].

Other options: --loglevel, --logdir, --quiet (no console output), --no-file (no rotating file handler) and --mailer (email alerts on CRITICAL records).

Apply the resulting configuration in your application:

importloggingfromlogging.configimportdictConfigfromnavconfig.loggingimportlogging_configdictConfig(logging_config)
logger=logging.getLogger("MY_APP")
logger.info("Hello World")

Console output uses colored formatting by default:

[INFO] 2024-03-11 19:31:39,408 MY_APP: Hello World

Accessing configuration

fromnavconfigimportconfigAPP_NAME=config.get("APP_NAME")
# "MyApp"

Attribute-style access also works:

APP_NAME=config.APP_NAME

Typed accessors

config.get("APP_NAME") # strconfig.getint("PORT", fallback=8080) # intconfig.getboolean("DEBUG") # boolconfig.getlist("ALLOWED_HOSTS") # list (comma-separated)config.getdict("EXTRA") # dict

An optional fallback argument is returned when the key is not found:

config.get("MISSING_KEY", "default_value")

Initialization

NavConfig resolves the project layout and loads the environment the first time one of its package-level names is accessed (config, BASE_DIR, DEBUG, ENV, ...), not while the package is being imported. Call navconfig.bootstrap() when you need the environment loaded into os.environ as a side effect without touching any of those names:

importnavconfignavconfig.bootstrap()

Configuration directories

By default NavConfig looks for files relative to the project root:

File typeDefault location
.envenv/ (plus ENV subdirectory)
.yml / .tomlenv/
pyproject.tomlproject root
.inietc/

A typical project looks like this:

myapp/
|-- __init__.py
|-- pyproject.toml
|-- env/
| |-- .env (shared base file)
| |-- dev/
| | +-- .env
| |-- staging/
| | +-- .env
| +-- prod/
| +-- .env
|-- etc/
| +-- config.ini
|-- logs/
+-- settings/
|-- __init__.py
+-- settings.py (optional)

Custom settings module

You can create a Python package called settings in your project to define additional configuration derived from NavConfig values.

Inside settings/settings.py:

importsysfromnavconfigimportconfig, DEBUGLOCAL_DEVELOPMENT=DEBUGisTrueandsys.argv[0] =="run.py"SEND_NOTIFICATIONS=config.get("SEND_NOTIFICATIONS", fallback=True)

Variables defined there are accessible through navconfig.conf:

fromnavconfig.confimportLOCAL_DEVELOPMENTifLOCAL_DEVELOPMENT:
print("Running in local development mode.")

Dependencies

  • Python >= 3.10
  • python-dotenv
  • configparser
  • PyYAML
  • pytomlpp
  • orjson
  • cryptography / pycryptodomex
  • hvac (HashiCorp Vault client)

Optional: redis, python-logstash-async, uvloop.

Contribution guidelines

Please see the Contribution Guide for details on:

  • Writing tests
  • Code review process
  • Other guidelines

License

NavConfig is released under the MIT License.

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

NavConfig

NavConfig is a configuration management library for Python projects. It is the default configuration layer of the Navigator Framework, but it works perfectly as a stand-alone tool for any Python application.

NavConfig can load configuration directives from multiple sources (and combine them):

  • Environment files (.env)
  • INI files (via configparser)
  • TOML and YAML files
  • pyproject.toml
  • Redis
  • HashiCorp Vault
  • Python settings modules (settings/settings.py)

The main goal of NavConfig is to centralize configuration access through a single, immutable point of truth that can be shared across modules.

Documentation: https://phenobarbital.github.io/navconfig/

Motivation

Applications require many configuration options. Some of those options hold secrets or credentials and must be kept separate from general settings. Configuration also varies between environments (development, staging, production).

NavConfig addresses this by loading secrets from .env files and structured settings from INI/TOML/YAML files, keeping concerns separated. It also supports retrieving configuration from external stores such as Redis or HashiCorp Vault.

Installation

pip install navconfig

To include optional backends:

# Redis support
pip install navconfig[redis]
# HashiCorp Vault support
pip install navconfig[hvac]
# Logstash logging
pip install navconfig[logstash]
# All features
pip install navconfig[all]

The kardex CLI

NavConfig ships a command line tool called kardex that bootstraps and maintains the configuration layout of a project. It is organised in command groups, each with its own actions:

CommandWhat it does
kardex env createCreate the default project structure.
kardex env create --splitSame, plus the supplementary .env.* files.
kardex env new <name>Add an environment from the shared env/.env.
kardex vault createWrite the HashiCorp Vault directives into env/<env>/.env.
kardex vault migratePush the variables of an environment into Vault.
kardex vault save VAR:VALUEStore one or more variables in Vault.
kardex log enableEnable the [logging] section of etc/config.ini.

Run kardex <group> <action> --help for the full list of options.

Upgrading from 2.x:kardex create and kardex new-env were removed in 3.0. Use kardex env create and kardex env new instead.

Quickstart

1. Create the project structure

kardex env create --env dev

This generates the following structure in the current directory:

.
|-- env/
| |-- .env (shared across environments, no ENV= pin)
| +-- dev/
| +-- .env (pinned to ENV=dev)
|-- etc/
| +-- config.ini
|-- logs/
+-- templates/
  • env/.env -- values shared by every environment. kardex env new uses it as the template for new environments.
  • env/dev/.env -- environment variables (secrets, feature flags, paths).
  • etc/config.ini -- INI-based settings consumed by NavConfig, including a [logging] section.
  • logs/ -- default directory where rotating log files are written.
  • templates/ -- default directory for template files.

Existing files are never overwritten; pass --force when you do want them replaced. Use --path to point at a different project root:

kardex env create --env dev --path /srv/myapp

2. Split the configuration across several files (optional)

NavConfig loads .env first and then a set of supplementary files, which keeps unrelated concerns apart. Pass --split to create them all:

kardex env create --env dev --split
env/dev/
|-- .env base configuration and Vault credentials
|-- .env.resources paths and resource-level directives
|-- .env.databases database connection settings
|-- .env.api HTTP layer settings
|-- .env.cache Redis / cache backend settings
+-- .env.local local overrides, loaded last (keep out of git)

Files are loaded in that order, so .env.local always wins. Everything ends up in the same flat namespace, so config.get("DBHOST") works regardless of which file declared it.

3. Add more environments

kardex env new prod
kardex env new staging --split

This copies env/.env (or the bundled sample if no shared file exists) into env/<name>/.env, adjusting the ENV variable automatically.

4. Select an environment

Set the ENV variable before starting your application:

ENV=prod python app.py

NavConfig loads env/prod/.env and any INI file referenced by its CONFIG_FILE directive.

HashiCorp Vault

Configure the connection

kardex vault create --env dev \
--url http://vault.internal:8200 \
--token "$VAULT_TOKEN" \
--mount-point myapp

This appends a delimited block to env/dev/.env (creating the file if it does not exist yet) with the directives NavConfig reads:

VAULT_ENABLED=true
VAULT_URL=http://vault.internal:8200
VAULT_TOKEN=...
VAULT_MOUNT_POINT=myapp
VAULT_VERSION=2
# VAULT_ENV=

Secrets are then read from <VAULT_MOUNT_POINT>/<ENV>/, and merged on top of the file-based values. Set VAULT_ENV to read from a different path segment than ENV; set NAVCONFIG_FILE_OVERRIDE_ENABLED=true to let the .env files win over Vault instead.

Re-running the command updates only the directives you pass on the command line, which makes token rotation a one-liner. Pass --force to rewrite the whole block.

Migrate an existing .env into Vault

kardex vault migrate --env dev --dry-run # inspect first
kardex vault migrate --env dev

Two families of variables are deliberately left in the .env file, because NavConfig needs them before it can reach Vault:

  • the Vault directives themselves (VAULT_*), and
  • the bootstrap directives (ENV, CONFIG_FILE, SITE_ROOT, ...), which can be included anyway with --include-bootstrap.

Useful options: --include-extra (also migrate the .env.* files), --keep-existing (never overwrite a key already stored in Vault), --file (migrate an arbitrary file) and --yes (skip the confirmation prompt).

Values are masked in the output, and nothing is written until you confirm.

Store single variables

kardex vault save DB_PASSWORD:s3cr3t
kardex vault save "DSN:postgres://user:pass@host:5432/db" API_KEY:abc123

Only the first colon separates the name from the value, so values may contain colons themselves.

Logging

Enable the logging facility with:

kardex log enable

That writes the [logging] section of etc/config.ini (creating the file from the bundled sample when missing) with console and rotating-file output enabled, and creates the log directory. Comments in the INI file are kept.

To also forward records to a Logstash server:

kardex log enable --logstash \
--logstash-host logs.internal \
--logstash-port 5044 \
--logstash-level INFO

The Logstash handler requires pip install navconfig[logstash].

Other options: --loglevel, --logdir, --quiet (no console output), --no-file (no rotating file handler) and --mailer (email alerts on CRITICAL records).

Apply the resulting configuration in your application:

importloggingfromlogging.configimportdictConfigfromnavconfig.loggingimportlogging_configdictConfig(logging_config)
logger=logging.getLogger("MY_APP")
logger.info("Hello World")

Console output uses colored formatting by default:

[INFO] 2024-03-11 19:31:39,408 MY_APP: Hello World

Accessing configuration

fromnavconfigimportconfigAPP_NAME=config.get("APP_NAME")
# "MyApp"

Attribute-style access also works:

APP_NAME=config.APP_NAME

Typed accessors

config.get("APP_NAME") # strconfig.getint("PORT", fallback=8080) # intconfig.getboolean("DEBUG") # boolconfig.getlist("ALLOWED_HOSTS") # list (comma-separated)config.getdict("EXTRA") # dict

An optional fallback argument is returned when the key is not found:

config.get("MISSING_KEY", "default_value")

Initialization

NavConfig resolves the project layout and loads the environment the first time one of its package-level names is accessed (config, BASE_DIR, DEBUG, ENV, ...), not while the package is being imported. Call navconfig.bootstrap() when you need the environment loaded into os.environ as a side effect without touching any of those names:

importnavconfignavconfig.bootstrap()

Configuration directories

By default NavConfig looks for files relative to the project root:

File typeDefault location
.envenv/ (plus ENV subdirectory)
.yml / .tomlenv/
pyproject.tomlproject root
.inietc/

A typical project looks like this:

myapp/
|-- __init__.py
|-- pyproject.toml
|-- env/
| |-- .env (shared base file)
| |-- dev/
| | +-- .env
| |-- staging/
| | +-- .env
| +-- prod/
| +-- .env
|-- etc/
| +-- config.ini
|-- logs/
+-- settings/
|-- __init__.py
+-- settings.py (optional)

Custom settings module

You can create a Python package called settings in your project to define additional configuration derived from NavConfig values.

Inside settings/settings.py:

importsysfromnavconfigimportconfig, DEBUGLOCAL_DEVELOPMENT=DEBUGisTrueandsys.argv[0] =="run.py"SEND_NOTIFICATIONS=config.get("SEND_NOTIFICATIONS", fallback=True)

Variables defined there are accessible through navconfig.conf:

fromnavconfig.confimportLOCAL_DEVELOPMENTifLOCAL_DEVELOPMENT:
print("Running in local development mode.")

Dependencies

  • Python >= 3.10
  • python-dotenv
  • configparser
  • PyYAML
  • pytomlpp
  • orjson
  • cryptography / pycryptodomex
  • hvac (HashiCorp Vault client)

Optional: redis, python-logstash-async, uvloop.

Contribution guidelines

Please see the Contribution Guide for details on:

  • Writing tests
  • Code review process
  • Other guidelines

License

NavConfig is released under the MIT License.

Releases

Packages

Used by

Contributors

Languages