This file provides guidance to Claude Code when working with code in this repository.
msgspec-ext is a high-performance settings management library built on top of msgspec. It provides a pydantic-like API for loading settings from environment variables and .env files, with 3.8x better performance than pydantic-settings.
msgspec-ext/
├── src/msgspec_ext/ # Source code
│ ├── settings.py # Core BaseSettings implementation
│ └── version.py # Version string
├── tests/ # Test suite
│ └── test_settings.py # Comprehensive unit tests (22 tests)
├── examples/ # Practical examples
│ ├── 01_basic_usage.py
│ ├── 02_env_prefix.py
│ ├── 03_dotenv_file.py
│ ├── 04_advanced_types.py
│ ├── 05_serialization.py
│ └── README.md
├── scripts/ # Automation scripts
│ ├── release.sh # Release automation (use this!)
│ └── setup-branch-protection.sh
└── benchmark.py # Performance benchmarks
## Common Commands
### Development
```bash
# Install dependencies
uv sync
# Install with dev dependencies
uv sync --group dev
# Add new dependency
uv add <package>
# Run tests
uv run pytest tests/ -v
# Run specific test
uv run pytest tests/test_settings.py::test_name -v
# Run linter (only checks src/)
uv run ruff check src/
# Format code
uv run ruff format
# Run benchmark
uv run python benchmark.py
All tests are in tests/test_settings.py:
- 22 comprehensive unit tests
- Tests cover: basic usage, env vars, type conversion, .env files, validation, serialization
- Run with:
uv run pytest tests/ -v - Should complete in < 0.1s
IMPORTANT: Always use the release script:
# Create a new release
./scripts/release.sh <version>
# Example:
./scripts/release.sh 0.2.0The script will:
- Update
src/msgspec_ext/version.py - Create git tag
- Push to upstream
- Trigger GitHub Actions for PyPI publishing
Never manually edit version.py - always use the release script!
Key optimization: Uses bulk JSON decoding instead of field-by-field validation.
# Old approach (slow):
for field in fields:
value = msgspec.convert(env_value, field_type) # Python loop
# New approach (fast):
json_bytes = encoder.encode(all_values) # Cached encoder
return decoder.decode(json_bytes) # Cached decoder, all in C!Important classes:
BaseSettings: Wrapper factory that creates msgspec.Struct instancesSettingsConfigDict: Configuration (env_file, env_prefix, etc.)
Performance optimizations:
- Cached encoders/decoders (ClassVar)
- Automatic field ordering (required before optional)
- Bulk JSON decoding in C
- Zero Python loops for validation
Environment variables are always strings, but we need proper types:
_preprocess_env_value(env_value: str, field_type: type) -> AnyHandles:
bool: "true"/"false"/"1"/"0" → True/Falseint/float: String to number conversionlist/dict: JSON parsing for complex typesOptional[T]: Unwraps Union types correctly
msgspec.defstruct requires required fields before optional fields. We handle this automatically in _create_struct_class():
required_fields = [(name, type), ...]
optional_fields = [(name, type, default), ...]
fields = required_fields + optional_fields # Correct orderRuff configuration (pyproject.toml):
- Target: Python 3.10+
- Line length: 88
- Strict linting for
src/ - Relaxed rules for
tests/andexamples/
Running lint:
# Check only src/ (recommended for quick checks)
uv run ruff check src/
# Check everything
uv run ruff check
# Auto-fix issues
uv run ruff check --fix
# Format code
uv run ruff formatImportant per-file ignores:
tests/: Ignores D (docstrings), S104 (binding), S105 (passwords), etc.examples/: Ignores D, S101, S104, S105, T201, F401benchmark.py: Ignores D, S101, S105, T201, etc.
5 practical examples in examples/:
- Basic usage: Defaults, env vars, explicit values
- Environment prefixes: Organizing settings with
env_prefix - .env files: Loading from dotenv files
- Advanced types: Optional, lists, dicts, JSON parsing
- Serialization:
model_dump(),model_dump_json(),schema()
Run examples:
uv run python examples/01_basic_usage.pyuv run python benchmark.pyExpected results:
- msgspec-ext: ~0.7ms per load
- pydantic-settings: ~2.7ms per load (if installed)
- Speedup: 3.8x faster than pydantic-settings
GitHub Actions workflows:
- CI: Runs on every push (Ruff, Tests, Build)
- Publish: Publishes to PyPI on new tags
- Release Drafter: Auto-generates release notes
Creating a PR:
- Create feature branch from main
- Make changes
- Run tests:
uv run pytest tests/ -v - Run lint:
uv run ruff check src/ - Format:
uv run ruff format - Push and create PR
- Ensure CI passes (Ruff, Tests, Build)
- Create branch:
git checkout -b feat/feature-name - Implement feature in
src/msgspec_ext/settings.py - Add tests in
tests/test_settings.py - Add example in
examples/(if user-facing) - Run tests:
uv run pytest tests/ -v - Run lint:
uv run ruff check src/ - Create PR
- Add failing test in
tests/test_settings.py - Fix bug in
src/msgspec_ext/settings.py - Verify test passes
- Run full test suite
- Create PR
# Update specific package
uv add package@latest
# Update all dependencies
uv sync --upgrade- Always use cached encoder/decoder: Don't create new instances
- Bulk operations: Process all fields at once via JSON decode
- Avoid Python loops: Let msgspec handle validation in C
- Field ordering: Required before optional (automatic)
- Type hints: Proper annotations enable better performance
Tests failing:
uv run pytest tests/ -v # See detailed outputLint errors:
uv run ruff check src/ # Check src only
uv run ruff check --fix # Auto-fixImport errors:
uv sync # Reinstall dependenciesPerformance regression:
uv run python benchmark.py # Compare with baselinesrc/msgspec_ext/settings.py- Core implementation (most important)src/msgspec_ext/version.py- Version (updated by release script)tests/test_settings.py- Test suite (22 tests)benchmark.py- Performance benchmarksscripts/release.sh- Release automation (use this for releases!)pyproject.toml- Project config, dependencies, ruff settings
README.md- User-facing docs with quickstartexamples/README.md- Example gallery guideCONTRIBUTING.md- Contribution guidelinesCLAUDE.md- This file (for Claude Code)
- Releases: Always use
./scripts/release.sh <version>, never edit version.py manually - Linting: Focus on
src/only (uv run ruff check src/) - Tests: Must maintain 100% pass rate (22/22)
- Performance: Benchmark should show ~0.7ms per load, 3.8x vs pydantic
- Examples: Update if changing user-facing APIs
- Breaking changes: Require major version bump