Secrets

argclass provides built-in support for handling sensitive values securely.

Secret Arguments

Use argclass.Secret() to mark sensitive arguments:

import argclass

class Parser(argclass.Parser):
    username: str
    password: str = argclass.Secret(help="Database password")
    api_key: str = argclass.Secret()

parser = Parser()
parser.parse_args(["--username", "admin", "--password", "secret123", "--api-key", "key456"])

assert parser.username == "admin"
assert str(parser.password) == "secret123"
assert str(parser.api_key) == "key456"

Or use the secret=True parameter:

import argclass

class Parser(argclass.Parser):
    password: str = argclass.Argument(
        secret=True,
        help="Database password"
    )

parser = Parser()
parser.parse_args(["--password", "supersecret"])

assert str(parser.password) == "supersecret"

SecretString Type

Secret string values are wrapped in SecretString:

import argclass
from argclass import SecretString

class Parser(argclass.Parser):
    password: str = argclass.Secret()

parser = Parser()
parser.parse_args(["--password", "supersecret"])

# Value is wrapped
assert isinstance(parser.password, SecretString)
assert repr(parser.password) == "'******'"
assert str(parser.password) == "supersecret"

Preventing Accidental Logging

SecretString prevents accidental exposure when used with logging:

from argclass import SecretString

password = SecretString("supersecret")

# repr always shows masked value
assert repr(password) == "'******'"

# Use !r in f-strings for safe output
assert f"{password!r}" == "'******'"

# str() returns the actual value - use with caution
actual = str(password)
assert actual == "supersecret"

Secrets from Environment

Combine secrets with environment variables:

import os
import argclass

os.environ["DB_PASSWORD"] = "secret_db_pass"
os.environ["API_KEY"] = "key123"

class Parser(argclass.Parser):
    db_password: str = argclass.Secret(
        env_var="DB_PASSWORD",
        help="Database password"
    )
    api_key: str = argclass.Secret(
        env_var="API_KEY",
        help="API authentication key"
    )

parser = Parser()
parser.parse_args([])

assert str(parser.db_password) == "secret_db_pass"
assert str(parser.api_key) == "key123"

# Clean up environment after parsing
parser.sanitize_env()

assert "DB_PASSWORD" not in os.environ
assert "API_KEY" not in os.environ

Secrets from Config Files

Secrets can also come from config files (use with caution):

import argclass
from pathlib import Path
from tempfile import NamedTemporaryFile

class DatabaseGroup(argclass.Group):
    host: str = "localhost"
    password: str = argclass.Secret()

class Parser(argclass.Parser):
    api_key: str = argclass.Secret()
    database = DatabaseGroup()

# Create config file
with NamedTemporaryFile(mode="w", suffix=".ini", delete=False) as f:
    f.write("[DEFAULT]\n")
    f.write("api_key = your-secret-key\n\n")
    f.write("[database]\n")
    f.write("password = db-password\n")
    config_path = f.name

parser = Parser(config_files=[config_path])
parser.parse_args([])

assert str(parser.api_key) == "your-secret-key"
assert str(parser.database.password) == "db-password"

Path(config_path).unlink()

Secret Groups

Group related secrets:

import argclass

class CredentialsGroup(argclass.Group):
    username: str
    password: str = argclass.Secret()
    token: str = argclass.Secret()

class Parser(argclass.Parser):
    credentials = CredentialsGroup()

parser = Parser()
parser.parse_args([
    "--credentials-username", "admin",
    "--credentials-password", "secret123",
    "--credentials-token", "token456"
])

assert parser.credentials.username == "admin"
assert str(parser.credentials.password) == "secret123"
assert str(parser.credentials.token) == "token456"

Comparison Methods

SecretString supports comparison without exposing values:

from argclass import SecretString

secret1 = SecretString("password123")
secret2 = SecretString("password123")
secret3 = SecretString("different")

# Comparisons work
assert secret1 == secret2
assert secret1 != secret3
assert secret1 == "password123"

# repr is always safe
assert repr(secret1) == "'******'"

Best Practices

1. Use Environment Variables

Prefer environment variables over config files for secrets:

import os
import argclass

os.environ["API_KEY"] = "from_environment"

class Parser(argclass.Parser):
    # Good: from environment
    api_key: str = argclass.Secret(env_var="API_KEY")

parser = Parser()
parser.parse_args([])

assert str(parser.api_key) == "from_environment"

del os.environ["API_KEY"]

2. Sanitize After Parsing

Danger

Critical: Environment variables are inherited by child processes. If your application spawns subprocesses, runs shell commands, or calls external tools, those processes can read your secrets from the environment.

Call sanitize_env() immediately after parsing and before any subprocess calls.

Remove secrets from the environment:

import os
import argclass

os.environ["SECRET_VALUE"] = "sensitive"

class Parser(argclass.Parser):
    secret: str = argclass.Secret(env_var="SECRET_VALUE")

parser = Parser()
parser.parse_args([])

assert "SECRET_VALUE" in os.environ
parser.sanitize_env()  # Removes all used env vars
assert "SECRET_VALUE" not in os.environ

Automatic Sanitization During Parsing

Use sanitize_secrets=True to automatically remove secret environment variables during parsing:

import os
import argclass

os.environ["SECRET_VALUE"] = "sensitive"
os.environ["PUBLIC_VALUE"] = "not_secret"

class Parser(argclass.Parser):
    secret: str = argclass.Secret(env_var="SECRET_VALUE")
    public: str = argclass.Argument(env_var="PUBLIC_VALUE")

parser = Parser()
parser.parse_args([], sanitize_secrets=True)

# Secret env var is removed automatically
assert "SECRET_VALUE" not in os.environ
# Non-secret env var remains
assert os.environ["PUBLIC_VALUE"] == "not_secret"

# Values are still accessible
assert str(parser.secret) == "sensitive"
assert parser.public == "not_secret"

del os.environ["PUBLIC_VALUE"]

Selective Sanitization

Use sanitize_env(only_secrets=True) to remove only secret environment variables while keeping non-secret ones:

import os
import argclass

os.environ["SECRET_VALUE"] = "sensitive"
os.environ["PUBLIC_VALUE"] = "not_secret"

class Parser(argclass.Parser):
    secret: str = argclass.Secret(env_var="SECRET_VALUE")
    public: str = argclass.Argument(env_var="PUBLIC_VALUE")

parser = Parser()
parser.parse_args([])

# Remove only secret env vars
parser.sanitize_env(only_secrets=True)

# Secret env var is removed
assert "SECRET_VALUE" not in os.environ
# Non-secret env var remains
assert os.environ["PUBLIC_VALUE"] == "not_secret"

del os.environ["PUBLIC_VALUE"]

3. Use repr for Safe Logging

Always use !r in f-strings for safe output:

import argclass

class Parser(argclass.Parser):
    password: str = argclass.Secret()

parser = Parser()
parser.parse_args(["--password", "supersecret"])

# Safe - use !r format specifier
safe_output = f"Password: {parser.password!r}"
assert "supersecret" not in safe_output
assert "******" in safe_output