API Reference
CheapSettings
cheap_settings.CheapSettings
Base class for simple, environment-variable-driven configuration.
Subclass this and define your settings as typed class attributes:
class MySettings(CheapSettings):
host: str = "localhost"
port: int = 8080
debug: bool = False
Environment variables will override the defaults
HOST=example.com PORT=3000 DEBUG=true python myapp.py
Supports all basic Python types plus datetime, date, time, Decimal, UUID, Path, Optional and Union types. Complex types (list, dict) are parsed from JSON strings.
Source code in src/cheap_settings/cheap_settings.py
449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 | |
__getattribute__(name)
Allow instances to access class-level settings.
Source code in src/cheap_settings/cheap_settings.py
466 467 468 469 470 471 472 473 474 | |
__getstate__()
Get the state for pickling - returns a dict of all settings.
Source code in src/cheap_settings/cheap_settings.py
494 495 496 497 498 | |
__reduce__()
Enable pickling by returning class and state information.
Source code in src/cheap_settings/cheap_settings.py
476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 | |
__setstate__(state)
Restore state when unpickling.
Source code in src/cheap_settings/cheap_settings.py
500 501 502 503 504 | |
from_env()
classmethod
Create a static snapshot with only values from environment variables.
Returns a regular Python class containing only the settings that are explicitly set in environment variables, ignoring all defaults.
Returns:
| Name | Type | Description |
|---|---|---|
object |
object
|
A new class with only environment-sourced settings. |
This is useful for: - Debugging which settings are coming from the environment - Creating minimal configuration objects - Validating environment-only deployments
Example
os.environ['HOST'] = 'example.com' EnvOnly = MySettings.from_env() EnvOnly.host # 'example.com' hasattr(EnvOnly, 'port') # False (not in env)
Source code in src/cheap_settings/cheap_settings.py
557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 | |
set_config_from_command_line(arg_parser=None, args=None)
classmethod
Creates command line arguments (as flags) that correspond to the settings, & parses them, setting the
config values based on them. Settings overridden by command line arguments take precedence over any
default variables, & over environment variables. Currently, settings of dict & list types are ignored,
& no command line arguments are added for them. It can optionally take an instance of argparse.ArgumentParser
that can be used to pre-configure your own command line arguments. The optional args parameter allows
passing specific arguments for testing (if None, uses sys.argv). Returns the parsed arguments (an
instance of argparse.Namespace). Can raise various exceptions.
Source code in src/cheap_settings/cheap_settings.py
617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 | |
set_raise_on_uninitialized(value=True)
classmethod
Set whether to raise an error when accessing uninitialized settings.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
bool
|
If True, accessing uninitialized settings raises AttributeError. If False, accessing uninitialized settings returns None (default). |
True
|
Example
class MySettings(CheapSettings): ... required_setting: str # No default MySettings.set_raise_on_uninitialized(True) MySettings.required_setting # Raises AttributeError
Source code in src/cheap_settings/cheap_settings.py
598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 | |
to_static()
classmethod
Create a static snapshot of current settings as a regular class. The returned class is a regular Python class without any dynamic behavior.
Returns:
| Name | Type | Description |
|---|---|---|
object |
Type[Any]
|
a new class with all settings resolved to their current values. |
This is useful for: - Performance-critical code where attribute access overhead matters - Situations where you want to freeze settings at a point in time - Working around edge cases with the dynamic metaclass behavior
Example
class MySettings(CheapSettings): ... host: str = "localhost" ... port: int = 8080 StaticSettings = MySettings.to_static() StaticSettings.host # Just a regular class attribute 'localhost'
Source code in src/cheap_settings/cheap_settings.py
506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 | |
Reserved Names
The attribute name __cheap_settings__ is reserved for future configuration of cheap-settings behavior. Do not use this name for your settings.
# ❌ Don't do this
class MySettings(CheapSettings):
__cheap_settings__: dict = {} # Reserved!
# ✅ Do this instead
class MySettings(CheapSettings):
my_settings: dict = {}
Type Support
cheap-settings automatically converts environment variable strings to the appropriate Python types based on type annotations.
Supported Types
| Type | Example | Environment Variable | Notes |
|---|---|---|---|
str |
"hello" |
VALUE="hello" |
No conversion needed |
int |
42 |
VALUE="42" |
Converted with int() |
float |
3.14 |
VALUE="3.14" |
Converted with float() |
bool |
True |
VALUE="true" |
Accepts: true/false, yes/no, on/off, 1/0 (case-insensitive) |
pathlib.Path |
Path("/etc") |
VALUE="/etc" |
Converted with Path() |
datetime |
datetime(2024, 12, 25, 15, 30) |
VALUE="2024-12-25T15:30:00" |
ISO format (fromisoformat) |
date |
date(2024, 12, 25) |
VALUE="2024-12-25" |
ISO format (YYYY-MM-DD) |
time |
time(15, 30, 45) |
VALUE="15:30:45" |
ISO format (HH:MM:SS) |
Decimal |
Decimal("99.99") |
VALUE="99.99" |
Preserves precision |
UUID |
UUID("...") |
VALUE="550e8400-..." |
With/without hyphens |
list |
[1, 2, 3] |
VALUE='[1, 2, 3]' |
Parsed as JSON |
dict |
{"key": "value"} |
VALUE='{"key": "value"}' |
Parsed as JSON |
| Custom types | Any type with from_string() |
VALUE="custom format" |
Calls Type.from_string(value) |
Optional[T] |
None or T |
VALUE="none" or valid T |
Special "none" string sets to None |
Union[T, U] |
T or U |
Valid for either type | Tries each type in order |
Extended Type Examples
Date and Time Types
from datetime import datetime, date, time
from cheap_settings import CheapSettings
class TimeSettings(CheapSettings):
created_at: datetime = datetime(2024, 1, 1)
expiry_date: date = date(2024, 12, 31)
backup_time: time = time(3, 0, 0)
# Environment variables:
# CREATED_AT="2024-12-25T15:30:45.123456" # With microseconds
# CREATED_AT="2024-12-25T15:30:45+05:30" # With timezone
# EXPIRY_DATE="2025-01-01"
# BACKUP_TIME="02:30:00"
Decimal for Financial Precision
from decimal import Decimal
class PricingSettings(CheapSettings):
product_price: Decimal = Decimal("99.99")
tax_rate: Decimal = Decimal("0.0825") # 8.25%
# Preserves exact decimal precision
# PRODUCT_PRICE="149.99"
# TAX_RATE="0.0925"
# Calculate with precision
tax = PricingSettings.product_price * PricingSettings.tax_rate
UUID for Unique Identifiers
from uuid import UUID
class ServiceSettings(CheapSettings):
instance_id: UUID = UUID("00000000-0000-0000-0000-000000000000")
# Accepts multiple formats:
# INSTANCE_ID="550e8400-e29b-41d4-a716-446655440000" # Standard
# INSTANCE_ID="550e8400e29b41d4a716446655440000" # No hyphens
# INSTANCE_ID="{550e8400-e29b-41d4-a716-446655440000}" # With braces
Custom Types with from_string()
Any custom type that implements a from_string() class method will work automatically:
class Temperature:
def __init__(self, celsius: float):
self.celsius = celsius
@classmethod
def from_string(cls, value: str) -> 'Temperature':
if value.endswith('F'):
# Convert Fahrenheit to Celsius
fahrenheit = float(value[:-1])
celsius = (fahrenheit - 32) * 5/9
return cls(celsius)
else:
# Assume Celsius
return cls(float(value))
class Settings(CheapSettings):
room_temp: Temperature = Temperature(20.0)
# Environment variables work automatically:
# ROOM_TEMP="72F" # Converts to Temperature(22.2)
# ROOM_TEMP="25" # Converts to Temperature(25.0)
Environment Variable Naming
Environment variables are the uppercase version of the attribute name:
class Settings(CheapSettings):
database_url: str = "localhost" # DATABASE_URL
api_timeout: int = 30 # API_TIMEOUT
enable_cache: bool = False # ENABLE_CACHE
Command Line Arguments
Command line arguments are the lowercase, hyphenated version of the attribute name:
class Settings(CheapSettings):
database_url: str = "localhost" # --database-url
api_timeout: int = 30 # --api-timeout
enable_cache: bool = False # --enable-cache / --no-enable-cache
Boolean Command Line Behavior
Boolean handling differs based on whether the type is Optional:
-
Non-Optional booleans (
bool = False/True) create both positive and negative flags:python class Settings(CheapSettings): debug: bool = False # Creates both --debug and --no-debug secure: bool = True # Creates both --secure and --no-secureThis allows you to override environment variables in either direction:bash # Environment: DEBUG=true, SECURE=false python app.py --no-debug --secure # Override both values -
Optional booleans (
Optional[bool]) accept explicit values:python class Settings(CheapSettings): debug: Optional[bool] = None # --debug true/false/1/0/yes/no
Both approaches allow overriding environment variables, but non-Optional booleans provide a cleaner flag-based interface.
Inheritance
Settings classes support inheritance. Child classes inherit all settings from parent classes and can override them:
class BaseSettings(CheapSettings):
timeout: int = 30
class WebSettings(BaseSettings):
timeout: int = 60 # Override parent
port: int = 8080 # Add new setting
Error Handling
- Type conversion errors: If an environment variable can't be converted to the expected type, a
ValueErroris raised with details - JSON parsing errors: For
listanddicttypes, JSON parsing errors include helpful messages - Missing attributes: Accessing undefined settings raises
AttributeError
Performance
For performance-critical code where attribute access overhead matters, use to_static() to create a snapshot with no dynamic behavior:
Settings = MyDynamicSettings.to_static()
# Now Settings.value is just a regular class attribute
Environment-Only Settings
The from_env() method returns a class containing only settings that are explicitly set in environment variables:
EnvOnly = MySettings.from_env()
# EnvOnly only has attributes for settings with environment variables
This is useful for debugging deployments or validating environment configuration.
Settings Without Initializers
You can define settings with type annotations but no default values:
class MySettings(CheapSettings):
required_api_key: str # No default value
optional_timeout: Optional[int] # No default, Optional type
By default, accessing uninitialized settings returns None:
assert MySettings.required_api_key is None
assert MySettings.optional_timeout is None
Environment variables work normally with uninitialized settings:
os.environ["REQUIRED_API_KEY"] = "secret123"
assert MySettings.required_api_key == "secret123"
For stricter validation, use set_raise_on_uninitialized(True) to make accessing uninitialized settings raise AttributeError:
MySettings.set_raise_on_uninitialized(True)
MySettings.required_api_key # Raises AttributeError if not in environment
Note: settings with Optional types (or unions with None), never raise when uninitialized.
They always return None even if you set_raise_on_uninitialized(True).
Working with Credentials and .env Files
For sensitive settings like API keys or passwords, use Optional types with no default:
class Settings(CheapSettings):
api_key: Optional[str] = None
db_password: Optional[str] = None
Since cheap-settings reads environment variables dynamically, it works seamlessly with python-dotenv:
from dotenv import load_dotenv
from cheap_settings import CheapSettings
load_dotenv() # Load .env file into environment
# Settings will automatically pick up the values