High-performance settings management and validation library extending msgspec
Project description
High-performance settings management and validation library powered by msgspec
📖 Read the announcement on Medium
Features
- ⚡ 7x faster than pydantic-settings - Built on msgspec's high-performance validation
- 🎯 26 built-in validators - Email, URLs, IP addresses, MAC addresses, dates, storage sizes, and more
- 🔧 Drop-in API compatibility - Familiar interface, easy migration from pydantic-settings
- 📦 All msgspec types supported - Full compatibility with msgspec's rich type system
- 🔐 Type-safe - Complete type hints and validation
- 📁 .env support - Fast built-in .env parser (169x faster cached loads)
- 🎨 Nested settings - Support for complex configuration structures
- 🪶 Zero dependencies - Only msgspec required
Installation
Using pip:
pip install msgspec-ext
Using uv (recommended):
uv add msgspec-ext
Quick Start
With BaseSettings (Environment Variables)
from msgspec_ext import BaseSettings, EmailStr, HttpUrl, PositiveInt
class AppSettings(BaseSettings):
# Basic types (msgspec native support)
name: str
debug: bool = False
# Numeric validators
port: PositiveInt = 8000 # Must be > 0
workers: PositiveInt = 4
# String validators
admin_email: EmailStr # RFC 5321 validation
api_url: HttpUrl # HTTP/HTTPS only
# Load from environment variables and .env file
settings = AppSettings()
print(settings.name) # my-app
print(settings.port) # 8000
print(settings.admin_email) # admin@example.com
# Serialize to dict
print(settings.model_dump())
# Output: {
# 'name': 'my-app',
# 'debug': False,
# 'port': 8000,
# 'workers': 4,
# 'admin_email': 'admin@example.com',
# 'api_url': 'https://api.example.com'
# }
# Serialize to JSON
print(settings.model_dump_json())
# Output: '{"name":"my-app","debug":false,"port":8000,"workers":4,"admin_email":"admin@example.com","api_url":"https://api.example.com"}'
Set environment variables:
export NAME="my-app"
export ADMIN_EMAIL="admin@example.com"
export API_URL="https://api.example.com"
With msgspec Structs (Direct Usage)
All validators work directly with msgspec structs for JSON/MessagePack serialization:
import msgspec
from msgspec_ext import EmailStr, IPv4Address, ByteSize, PositiveInt, dec_hook, enc_hook
class ServerConfig(msgspec.Struct):
host: IPv4Address
port: PositiveInt
admin_email: EmailStr
max_upload: ByteSize
# From JSON (use dec_hook for custom type conversion)
config = msgspec.json.decode(
b'{"host":"192.168.1.100","port":8080,"admin_email":"admin@example.com","max_upload":"50MB"}',
type=ServerConfig,
dec_hook=dec_hook
)
print(config.host) # 192.168.1.100
print(int(config.max_upload)) # 50000000 (50MB in bytes)
# To JSON (use enc_hook to serialize custom types)
json_bytes = msgspec.json.encode(config, enc_hook=enc_hook)
Type Support
msgspec-ext supports all msgspec native types plus 26 additional validators for common use cases.
Built-in msgspec Types
msgspec-ext has full compatibility with msgspec's extensive type system:
- Basic:
bool,int,float,str,bytes,bytearray - Collections:
list,tuple,set,frozenset,dict - Typing:
Optional,Union,Literal,Final,Annotated - Advanced:
datetime,date,time,timedelta,UUID,Decimal - msgspec:
msgspec.Raw,msgspec.UNSET(re-exported for convenience)
Plus many more - see the full list in msgspec documentation.
Custom Validators (26 types)
msgspec-ext adds 26 specialized validators for common validation scenarios:
🔢 Numeric Constraints (8 types)
from msgspec_ext import (
PositiveInt, NegativeInt, NonNegativeInt, NonPositiveInt,
PositiveFloat, NegativeFloat, NonNegativeFloat, NonPositiveFloat
)
class ServerSettings(BaseSettings):
port: PositiveInt # Must be > 0
offset: NegativeInt # Must be < 0
retry_count: NonNegativeInt # Can be 0 or positive
balance: NonPositiveFloat # Can be 0 or negative
🌐 Network & Hardware (4 types)
import msgspec
from msgspec_ext import IPv4Address, IPv6Address, IPvAnyAddress, MacAddress
# With BaseSettings
class NetworkSettings(BaseSettings):
server_ipv4: IPv4Address # 192.168.1.1
server_ipv6: IPv6Address # 2001:db8::1
proxy_ip: IPvAnyAddress # Accepts IPv4 or IPv6
device_mac: MacAddress # AA:BB:CC:DD:EE:FF
# Or with msgspec.Struct for API responses
class Device(msgspec.Struct):
name: str
ip: IPv4Address
mac: MacAddress
device = msgspec.json.decode(
b'{"name":"router-01","ip":"192.168.1.1","mac":"AA:BB:CC:DD:EE:FF"}',
type=Device,
dec_hook=dec_hook
)
✉️ String Validators (4 types)
from msgspec_ext import EmailStr, HttpUrl, AnyUrl, SecretStr
class AppSettings(BaseSettings):
admin_email: EmailStr # RFC 5321 validation
api_url: HttpUrl # HTTP/HTTPS only
webhook_url: AnyUrl # Any valid URL scheme
api_key: SecretStr # Masked in logs: **********
🗄️ Database & Connections (3 types)
from msgspec_ext import PostgresDsn, RedisDsn, PaymentCardNumber
class ConnectionSettings(BaseSettings):
database_url: PostgresDsn # postgresql://user:pass@host/db
cache_url: RedisDsn # redis://localhost:6379
card_number: PaymentCardNumber # Luhn validation + masking
📁 Path Validators (2 types)
from msgspec_ext import FilePath, DirectoryPath
class PathSettings(BaseSettings):
config_file: FilePath # Must exist and be a file
data_dir: DirectoryPath # Must exist and be a directory
💾 Storage & Dates (3 types)
import msgspec
from msgspec_ext import ByteSize, PastDate, FutureDate
from datetime import date
# With BaseSettings
class AppSettings(BaseSettings):
max_upload: ByteSize # Parse "10MB", "1GB", etc.
cache_size: ByteSize # Supports KB, MB, GB, KiB, MiB, GiB
founding_date: PastDate # Must be before today
launch_date: FutureDate # Must be after today
# Or with msgspec.Struct for configuration files
class StorageConfig(msgspec.Struct):
max_file_size: ByteSize
cache_limit: ByteSize
cleanup_after: int # days
config = msgspec.json.decode(
b'{"max_file_size":"100MB","cache_limit":"5GB","cleanup_after":30}',
type=StorageConfig,
dec_hook=dec_hook
)
print(int(config.max_file_size)) # 100000000
🎯 Constrained Strings (2 types)
from msgspec_ext import ConStr
class UserSettings(BaseSettings):
# With constraints
username: ConStr # Can use min_length, max_length, pattern
# Usage:
username = ConStr("alice", min_length=3, max_length=20, pattern=r"^[a-z0-9]+$")
Complete Validator List
| Category | Validators |
|---|---|
| Numeric | PositiveInt, NegativeInt, NonNegativeInt, NonPositiveInt, PositiveFloat, NegativeFloat, NonNegativeFloat, NonPositiveFloat |
| Network | IPv4Address, IPv6Address, IPvAnyAddress, MacAddress |
| String | EmailStr, HttpUrl, AnyUrl, SecretStr |
| Database | PostgresDsn, RedisDsn, PaymentCardNumber |
| Paths | FilePath, DirectoryPath |
| Storage & Dates | ByteSize, PastDate, FutureDate |
| Constrained | ConStr |
See examples/06_validators.py and examples/07_advanced_validators.py for complete usage examples.
Use Cases
API Request/Response Validation
import msgspec
from msgspec_ext import EmailStr, HttpUrl, PositiveInt, ByteSize, dec_hook, enc_hook
class CreateUserRequest(msgspec.Struct):
email: EmailStr
age: PositiveInt
website: HttpUrl
max_storage: ByteSize
class UserResponse(msgspec.Struct):
id: int
email: EmailStr
website: HttpUrl
# Validate incoming JSON
request = msgspec.json.decode(
b'{"email":"user@example.com","age":25,"website":"https://example.com","max_storage":"1GB"}',
type=CreateUserRequest,
dec_hook=dec_hook
)
print(request.email) # user@example.com
print(request.age) # 25
print(int(request.max_storage)) # 1000000000
# Serialize response
response = UserResponse(id=1, email=request.email, website=request.website)
json_bytes = msgspec.json.encode(response, enc_hook=enc_hook)
print(json_bytes)
# b'{"id":1,"email":"user@example.com","website":"https://example.com"}'
Configuration Files with Validation
import msgspec
from msgspec_ext import IPv4Address, PositiveInt, PostgresDsn, ByteSize, dec_hook
class ServerConfig(msgspec.Struct):
host: IPv4Address
port: PositiveInt
database_url: PostgresDsn
max_upload: ByteSize
workers: PositiveInt = 4
# Load from JSON config file
with open("config.json", "rb") as f:
config = msgspec.json.decode(f.read(), type=ServerConfig, dec_hook=dec_hook)
print(f"Server: {config.host}:{config.port}")
# Server: 192.168.1.50:8080
print(f"Max upload: {int(config.max_upload)} bytes")
# Max upload: 100000000 bytes
print(f"Workers: {config.workers}")
# Workers: 4
Message Queue Data Validation
import msgspec
from msgspec_ext import EmailStr, IPvAnyAddress, FutureDate, dec_hook, enc_hook
class ScheduledTask(msgspec.Struct):
task_id: str
notify_email: EmailStr
target_server: IPvAnyAddress
execute_at: FutureDate
# Serialize for queue (MessagePack is faster than JSON)
task = ScheduledTask(
task_id="task-123",
notify_email=EmailStr("admin@example.com"),
target_server=IPvAnyAddress("192.168.1.100"),
execute_at=FutureDate("2025-12-31")
)
msg_bytes = msgspec.msgpack.encode(task, enc_hook=enc_hook)
# Deserialize from queue
received_task = msgspec.msgpack.decode(msg_bytes, type=ScheduledTask, dec_hook=dec_hook)
Advanced Usage
Environment Variables & .env Files
from msgspec_ext import BaseSettings, SettingsConfigDict
class AppSettings(BaseSettings):
model_config = SettingsConfigDict(
env_file=".env", # Load from .env file
env_prefix="APP_", # Prefix for env vars
env_nested_delimiter="__" # Nested config separator
)
name: str
debug: bool = False
port: int = 8000
# Loads from APP_NAME, APP_DEBUG, APP_PORT
settings = AppSettings()
.env file:
APP_NAME=my-app
APP_DEBUG=true
APP_PORT=3000
APP_DATABASE__HOST=localhost
APP_DATABASE__PORT=5432
Standalone load_dotenv
from msgspec_ext import load_dotenv
# Load .env file into os.environ (does not override existing vars)
load_dotenv()
# Load from a custom path
load_dotenv("config/.env")
# Override existing environment variables
load_dotenv(".env", override=True)
# Custom encoding
load_dotenv(".env", encoding="latin-1")
Nested Configuration
from msgspec_ext import BaseSettings, SettingsConfigDict, PostgresDsn
class DatabaseSettings(BaseSettings):
host: str = "localhost"
port: int = 5432
name: str = "myapp"
url: PostgresDsn
class AppSettings(BaseSettings):
model_config = SettingsConfigDict(
env_file=".env",
env_nested_delimiter="__"
)
name: str = "My App"
debug: bool = False
database: DatabaseSettings
# Loads from DATABASE__HOST, DATABASE__PORT, DATABASE__URL, etc.
settings = AppSettings()
print(settings.name) # My App
print(settings.database.host) # localhost
print(settings.database.port) # 5432
# Full nested dump
print(settings.model_dump())
# Output: {
# 'name': 'My App',
# 'debug': False,
# 'database': {
# 'host': 'localhost',
# 'port': 5432,
# 'name': 'myapp',
# 'url': 'postgresql://user:pass@localhost:5432/myapp'
# }
# }
Secret Masking
from msgspec_ext import BaseSettings, SecretStr
class AppSettings(BaseSettings):
api_key: SecretStr
db_password: SecretStr
settings = AppSettings()
print(settings.api_key) # **********
print(settings.api_key.get_secret_value()) # actual-secret-key
print(settings.model_dump())
# Output: {'api_key': '**********', 'db_password': '**********'}
print(settings.model_dump_json())
# Output: '{"api_key":"**********","db_password":"**********"}'
Storage Size Parsing
from msgspec_ext import BaseSettings, ByteSize
class StorageSettings(BaseSettings):
max_upload: ByteSize
cache_limit: ByteSize
# Environment variables:
# MAX_UPLOAD=10MB
# CACHE_LIMIT=1GB
settings = StorageSettings()
print(int(settings.max_upload)) # 10000000 (10 MB in bytes)
print(int(settings.cache_limit)) # 1000000000 (1 GB in bytes)
print(settings.model_dump())
# Output: {'max_upload': 10000000, 'cache_limit': 1000000000}
Supported units: B, KB, MB, GB, TB, KiB, MiB, GiB, TiB
Date Validation
from msgspec_ext import BaseSettings, PastDate, FutureDate
from datetime import date, timedelta
class EventSettings(BaseSettings):
founding_date: PastDate # Must be before today
launch_date: FutureDate # Must be after today
# Environment variables:
# FOUNDING_DATE=2020-01-01
# LAUNCH_DATE=2025-12-31
settings = EventSettings()
JSON Parsing from Environment
from msgspec_ext import BaseSettings
class AppSettings(BaseSettings):
# Automatically parse JSON from environment variables
features: list[str] = ["auth", "api"]
limits: dict[str, int] = {"requests": 100}
config: dict[str, any] = {}
# Environment variable:
# FEATURES=["auth","api","payments"]
# LIMITS={"requests":1000,"timeout":30}
settings = AppSettings()
print(settings.features) # ['auth', 'api', 'payments']
Why Choose msgspec-ext?
msgspec-ext provides a faster, lighter alternative to pydantic-settings while offering more validators and maintaining a familiar API.
Performance Comparison
Cold start (first load, includes .env parsing):
| Library | Time per load | Speed |
|---|---|---|
| msgspec-ext | 0.353ms | 7.0x faster ⚡ |
| pydantic-settings | 2.47ms | Baseline |
Warm (cached) (repeated loads in long-running applications):
| Library | Time per load | Speed |
|---|---|---|
| msgspec-ext | 0.011ms | 169x faster ⚡ |
| pydantic-settings | 1.86ms | Baseline |
Benchmarks run on Google Colab. Includes .env parsing, environment variable loading, type validation, and nested configuration. Run
benchmark/benchmark_cold_warm.pyto reproduce.
Key Advantages
| Feature | msgspec-ext | pydantic-settings |
|---|---|---|
| Cold start | 7.0x faster ⚡ | Baseline |
| Warm (cached) | 169x faster ⚡ | Baseline |
| Validators | 26 built-in | ~15 |
| Package size | 0.49 MB | 1.95 MB |
| Dependencies | 1 (msgspec only) | 5+ |
| .env support | ✅ Built-in fast parser | ✅ Via python-dotenv |
| Type validation | ✅ msgspec C backend | ✅ Pydantic |
| Advanced caching | ✅ 169x faster | ❌ |
| Nested config | ✅ | ✅ |
| JSON Schema | ✅ | ✅ |
How is it so fast?
msgspec-ext achieves exceptional performance through:
- Bulk validation: Validates all fields at once in C (via msgspec), not one-by-one in Python
- Custom .env parser: Built-in fast parser with zero external dependencies (117.5x faster than pydantic)
- Smart caching: Caches .env files, field mappings, and type information - subsequent loads are 169x faster
- Zero overhead: Fast paths for common types with minimal Python code
This means:
- 🚀 CLI tools - 7.0x faster startup every invocation
- ⚡ Serverless functions - Lower cold start latency
- 🔄 Long-running apps - Reloading settings takes only 11 microseconds after first load!
Examples
Check out the examples/ directory for comprehensive examples:
01_basic_usage.py- Getting started with BaseSettings02_env_prefix.py- Using environment variable prefixes03_dotenv_file.py- Loading from .env files04_advanced_types.py- Optional, lists, dicts, JSON parsing05_serialization.py- model_dump(), model_dump_json(), schema()06_validators.py- String, numeric, path, and database validators (17 types)07_advanced_validators.py- Network, storage, and date validators (8 types)
Contributing
We welcome contributions! Please see CONTRIBUTING.md for guidelines.
License
MIT License - see LICENSE file for details.
Acknowledgments
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file msgspec_ext-0.5.1.tar.gz.
File metadata
- Download URL: msgspec_ext-0.5.1.tar.gz
- Upload date:
- Size: 377.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
39ab684c2d9bf564fb706a030bfefa7d5492b49433199821253430bb116ff453
|
|
| MD5 |
d7f629becbc491c67dae8a5be336af31
|
|
| BLAKE2b-256 |
e8b5147ef8cb9788a659567a925f4d80bbdac7c1ca1375e05277d9fbf3619cd8
|
Provenance
The following attestation bundles were made for msgspec_ext-0.5.1.tar.gz:
Publisher:
publish.yml on msgflux/msgspec-ext
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
msgspec_ext-0.5.1.tar.gz -
Subject digest:
39ab684c2d9bf564fb706a030bfefa7d5492b49433199821253430bb116ff453 - Sigstore transparency entry: 1155526690
- Sigstore integration time:
-
Permalink:
msgflux/msgspec-ext@587065bc05af1561ca133de803a6395dbf96c74b -
Branch / Tag:
refs/heads/main - Owner: https://github.com/msgflux
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@587065bc05af1561ca133de803a6395dbf96c74b -
Trigger Event:
workflow_run
-
Statement type:
File details
Details for the file msgspec_ext-0.5.1-py3-none-any.whl.
File metadata
- Download URL: msgspec_ext-0.5.1-py3-none-any.whl
- Upload date:
- Size: 22.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1d2b54c8323b62fe05374b76c2df3feb403cb03aa823d541b7f8ad9ec1b0724f
|
|
| MD5 |
63a7f0a462d585303cfe08779f4f11f9
|
|
| BLAKE2b-256 |
e56c6f73947318ba1d41cde989367780763960ccf743f22293ac44161a5484d3
|
Provenance
The following attestation bundles were made for msgspec_ext-0.5.1-py3-none-any.whl:
Publisher:
publish.yml on msgflux/msgspec-ext
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
msgspec_ext-0.5.1-py3-none-any.whl -
Subject digest:
1d2b54c8323b62fe05374b76c2df3feb403cb03aa823d541b7f8ad9ec1b0724f - Sigstore transparency entry: 1155526698
- Sigstore integration time:
-
Permalink:
msgflux/msgspec-ext@587065bc05af1561ca133de803a6395dbf96c74b -
Branch / Tag:
refs/heads/main - Owner: https://github.com/msgflux
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@587065bc05af1561ca133de803a6395dbf96c74b -
Trigger Event:
workflow_run
-
Statement type: