{"id":48066004,"url":"https://github.com/macanderson/confetti","last_synced_at":"2026-04-04T14:36:14.395Z","repository":{"id":309237983,"uuid":"1035231973","full_name":"macanderson/confetti","owner":"macanderson","description":"Manage environments, environment variables and environment secrets from multiple sources with ease.","archived":false,"fork":false,"pushed_at":"2025-08-10T19:21:30.000Z","size":156,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-08-10T19:24:51.219Z","etag":null,"topics":["cli","configuration","configuration-files","environment-variables","environments","python","secrets","secrets-management"],"latest_commit_sha":null,"homepage":"https://02Beta.com/confetti","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/macanderson.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-08-10T00:00:33.000Z","updated_at":"2025-08-10T19:21:35.000Z","dependencies_parsed_at":"2025-08-10T19:24:53.672Z","dependency_job_id":"74d24bf9-6270-4dc0-bae9-c756d2fc979f","html_url":"https://github.com/macanderson/confetti","commit_stats":null,"previous_names":["macanderson/confetti"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/macanderson/confetti","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/macanderson%2Fconfetti","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/macanderson%2Fconfetti/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/macanderson%2Fconfetti/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/macanderson%2Fconfetti/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/macanderson","download_url":"https://codeload.github.com/macanderson/confetti/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/macanderson%2Fconfetti/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31402997,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-04T10:20:44.708Z","status":"ssl_error","status_checked_at":"2026-04-04T10:20:06.846Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","configuration","configuration-files","environment-variables","environments","python","secrets","secrets-management"],"created_at":"2026-04-04T14:36:13.660Z","updated_at":"2026-04-04T14:36:14.384Z","avatar_url":"https://github.com/macanderson.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Confetti 🎉 - Environment-aware Configuration Management for Python 🐍\n\n[![PyPI Version](https://img.shields.io/pypi/v/confetti)](https://pypi.org/project/confetti/)\n[![Python Versions](https://img.shields.io/pypi/pyversions/confetti)](https://pypi.org/project/confetti/)\n[![License](https://img.shields.io/github/license/confetti-dev/confetti)](https://github.com/confetti-dev/confetti/blob/main/LICENSE)\n[![Tests](https://github.com/confetti-dev/confetti/workflows/Tests/badge.svg)](https://github.com/confetti-dev/confetti/actions)\n[![Coverage](https://codecov.io/gh/confetti-dev/confetti/branch/main/graph/badge.svg)](https://codecov.io/gh/confetti-dev/confetti)\n[![Code Style: Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)\n[![Documentation](https://img.shields.io/badge/docs-latest-blue)](https://confetti.readthedocs.io)\n\n**Confetti** is a powerful Python library for managing configuration from multiple sources with ease. It allows you to load configuration variables and secrets from various sources (environment files, JSON, YAML, INI, Redis, GitHub Actions) and merge them into a unified configuration object with conflict resolution, type conversion, and source tracking.\n\n## ✨ Features\n\n- **📁 Multiple Configuration Sources**: Support for `.env`, JSON, YAML, INI files, Redis, and GitHub environment variables\n- **🔄 Automatic Merging**: Intelligently merge configurations from multiple sources with configurable precedence\n- **🔍 Source Tracking**: Track where each configuration value came from with detailed provenance\n- **🎯 Flexible Filtering**: Use regex patterns and hierarchical specs to include/exclude configuration keys\n- **💾 Two-way Sync**: Not just read - write back changes to configuration sources\n- **🔧 Type Safety**: Automatic type conversion and validation\n- **🌍 Environment Management**: Organize configurations by environment (development, staging, production)\n- **⚡ Async Support**: Async/await support for remote sources\n- **🔌 Extensible**: Easy to add custom configuration sources\n- **📦 Zero Config**: Works out of the box with sensible defaults\n\n## 📋 Table of Contents\n\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Configuration Sources](#configuration-sources)\n- [Advanced Usage](#advanced-usage)\n- [API Reference](#api-reference)\n- [CLI Usage](#cli-usage)\n- [Contributing](#contributing)\n- [License](#license)\n\n## 🚀 Installation\n\n### Using pip\n\n```bash\npip install confetti\n```\n\n### Using uv (recommended)\n\n```bash\nuv add confetti\n```\n\n### Using Poetry\n\n```bash\npoetry add confetti\n```\n\n### Development Installation\n\n```bash\ngit clone https://github.com/confetti-dev/confetti.git\ncd confetti\npip install -e \".[dev]\"\n```\n\n## 🎯 Quick Start\n\n### Basic Usage\n\n```python\nfrom confetti import Config, Environment\nfrom pathlib import Path\n\n# Create an environment\nenv = Environment(\"development\")\n\n# Register configuration sources (in order of precedence)\nenv.register_sources(\n    Path(\".env\"),                    # Local environment variables\n    Path(\"config/base.yaml\"),        # Base configuration\n    Path(\"config/development.json\"), # Environment-specific config\n)\n\n# Get merged configuration\nconfig = env.get_config()\n\n# Access configuration values\ndatabase_url = config.get(\"DATABASE_URL\")\ndebug_mode = config.get(\"DEBUG\", default=False)\n\n# Get all configuration as a dictionary\nall_config = config.values()\n```\n\n### Direct Config Usage\n\n```python\nfrom confetti import Config\nfrom confetti.sources import EnvFileSource, YamlFileSource\n\n# Create sources directly\nenv_source = EnvFileSource(Path(\".env\"))\nyaml_source = YamlFileSource(Path(\"config.yaml\"))\n\n# Create config with registered sources\nconfig = Config([\n    RegisteredSource(source=env_source),\n    RegisteredSource(source=yaml_source),\n])\n\n# Materialize and use\nconfig.materialize()\nprint(config.get(\"API_KEY\"))\n```\n\n## 📚 Configuration Sources\n\n### Environment Files (.env)\n\n```python\nfrom pathlib import Path\n\nenv.register_source(Path(\".env\"))\nenv.register_source(Path(\".env.local\"))  # Local overrides\n```\n\n**.env file example:**\n```bash\nDATABASE_URL=postgresql://localhost:5432/mydb\nREDIS_URL=redis://localhost:6379\nAPI_KEY=secret_key_123\nDEBUG=true\n```\n\n### YAML Files\n\n```python\nenv.register_source(Path(\"config.yaml\"))\n```\n\n**config.yaml example:**\n```yaml\ndatabase:\n  host: localhost\n  port: 5432\n  name: myapp\n  pool_size: 10\n\ncache:\n  backend: redis\n  ttl: 3600\n\nfeatures:\n  - authentication\n  - notifications\n  - analytics\n```\n\n### JSON Files\n\n```python\nenv.register_source(Path(\"settings.json\"))\n```\n\n**settings.json example:**\n```json\n{\n  \"api\": {\n    \"version\": \"v1\",\n    \"timeout\": 30,\n    \"rate_limit\": 1000\n  },\n  \"features\": {\n    \"dark_mode\": true,\n    \"beta_features\": false\n  }\n}\n```\n\n### INI Files\n\n```python\nenv.register_source(Path(\"config.ini\"))\n```\n\n**config.ini example:**\n```ini\n[database]\nhost = localhost\nport = 5432\n\n[cache]\nbackend = redis\nttl = 3600\n```\n\n### Redis Key-Value Store\n\n```python\nenv.register_source(\"redis://localhost:6379\")\n\n# With authentication and database selection\nenv.register_source(\"redis://user:password@localhost:6379/0\")\n\n# With key prefix\nfrom confetti.sources import RedisKeyValueSource\nredis_source = RedisKeyValueSource(\n    \"redis://localhost:6379\",\n    prefix=\"myapp:\"\n)\n```\n\n### GitHub Environment Variables\n\n```python\nimport os\n\n# Requires GITHUB_TOKEN environment variable\nenv.register_source(\"github://owner/repo#production\")\n\n# Or provide token explicitly\nfrom confetti.sources import GitHubEnvSource\ngithub_source = GitHubEnvSource(\n    \"github://owner/repo#production\",\n    token=\"ghp_your_token_here\"\n)\n```\n\n## 🔧 Advanced Usage\n\n### Filtering Configuration Keys\n\n```python\nimport re\nfrom confetti import Filter\n\n# Include only database-related keys\nenv.register_source(\n    Path(\".env\"),\n    filter=Filter(include_regex=re.compile(r\"^DB_.*\"))\n)\n\n# Hierarchical filtering for structured sources\nenv.register_source(\n    Path(\"config.yaml\"),\n    filter=Filter(hierarchical_spec={\n        \"database\": {\n            \"host\": True,\n            \"port\": True,\n            # \"password\": False  # Exclude password\n        }\n    })\n)\n\n# Limit nesting depth\nenv.register_source(\n    Path(\"deeply_nested.json\"),\n    depth=2  # Only flatten up to 2 levels deep\n)\n```\n\n### Source Precedence and Merging\n\n```python\n# Sources registered later override earlier ones\nenv.register_sources(\n    Path(\"config/base.yaml\"),     # 1. Base configuration\n    Path(\"config/prod.yaml\"),     # 2. Production overrides\n    Path(\".env\"),                 # 3. Environment variables (highest precedence)\n)\n\nconfig = env.get_config()\n\n# Check where a value came from\nprovenance = config.provenance(\"DATABASE_URL\")\nif provenance:\n    print(f\"DATABASE_URL came from: {provenance.source_id}\")\n    print(f\"Loaded at: {provenance.timestamp_loaded}\")\n```\n\n### Writing Configuration Changes\n\n```python\n# Make changes\nconfig.set(\"API_KEY\", \"new_secret_key\")\nconfig.set(\"DEBUG\", False)\nconfig.unset(\"DEPRECATED_SETTING\")\n\n# Save changes back to sources\nconfig.save()\n\n# Or save to specific source\nconfig.set(\"REDIS_URL\", \"redis://newhost:6379\", source=\"path/to/.env\")\nconfig.save()\n```\n\n### Custom Configuration Sources\n\n```python\nfrom confetti.core.source import Source\nfrom typing import Dict, Any, Optional\n\nclass CustomSource:\n    \"\"\"Example custom configuration source.\"\"\"\n    \n    def __init__(self, source_id: str):\n        self.id = source_id\n        self.name = f\"custom:{source_id}\"\n        self.extension = None\n        self._data = {}\n        \n    def load(self, filter=None, depth=None) -\u003e Dict[str, Any]:\n        # Load your configuration here\n        return self._data\n        \n    def get(self, key: str) -\u003e Optional[Any]:\n        return self._data.get(key)\n        \n    def set(self, key: str, value: Any) -\u003e None:\n        self._data[key] = value\n        \n    def save(self) -\u003e None:\n        # Persist changes\n        pass\n        \n    # ... implement other required methods ...\n\n# Use custom source\ncustom = CustomSource(\"my_custom_source\")\nenv.add_source_type(custom)\n```\n\n### GitHub Environment Sync\n\n```python\n# Sync local config to GitHub environment\nconfig = env.get_config()\n\n# Dry run to see what would change\nchanges = config.save_to_github(\n    \"github://owner/repo#production\",\n    dry_run=True\n)\nprint(f\"Would set: {changes['set']}\")\nprint(f\"Would delete: {changes['delete']}\")\n\n# Apply changes\nconfig.save_to_github(\"github://owner/repo#production\")\n```\n\n### Environment-based Configuration\n\n```python\nimport os\n\n# Determine environment\ncurrent_env = os.getenv(\"APP_ENV\", \"development\")\n\n# Create environment-specific configuration\nenv = Environment(current_env)\n\n# Load base config and environment-specific overrides\nenv.register_sources(\n    Path(\"config/base.yaml\"),\n    Path(f\"config/{current_env}.yaml\"),\n    Path(\".env.local\"),  # Local overrides (not in version control)\n)\n\nconfig = env.get_config()\n```\n\n## 📖 API Reference\n\n### Core Classes\n\n#### `Environment`\n\nManages configuration sources for a specific environment.\n\n```python\nenv = Environment(name: str)\nenv.register_source(path_or_uri, filter=None, depth=None, name=None, is_writable=True)\nenv.register_sources(*paths_or_uris)\nenv.get_config() -\u003e Config\nenv.add_source_type(source: Source)\n```\n\n#### `Config`\n\nUnified configuration object with source tracking.\n\n```python\nconfig.get(key: str, default=None) -\u003e Any\nconfig.values() -\u003e Dict[str, Any]\nconfig.set(key: str, value: Any, source: str = None)\nconfig.unset(key: str)\nconfig.save()\nconfig.reload()\nconfig.provenance(key: str) -\u003e ProvenanceRecord\nconfig.remove_source(source_id: str)\nconfig.save_to_github(uri: str, token: str = None, dry_run: bool = False)\n```\n\n#### `Filter`\n\nFiltering rules for configuration keys.\n\n```python\nFilter(\n    include_regex: Pattern = None,\n    hierarchical_spec: Dict = None,\n    depth: int = None\n)\n```\n\n### Source Classes\n\nAll sources implement the `Source` protocol with these methods:\n\n- `load(filter=None, depth=None) -\u003e Dict[str, Any]`\n- `get(key: str) -\u003e Any`\n- `set(key: str, value: Any)`\n- `unset(key: str)`\n- `save()`\n- `reload()`\n- `exists(key: str) -\u003e bool`\n- `keys() -\u003e List[str]`\n- `values() -\u003e Dict[str, Any]`\n- `clear()`\n- `size() -\u003e int`\n\n## 💻 CLI Usage\n\nConfetti includes a CLI for managing configurations:\n\n```bash\n# List all configuration sources\nconfetti sources-list --env production\n\n# Get a specific configuration value\nconfetti get DATABASE_URL --env production\n\n# Set a configuration value\nconfetti set API_KEY \"new_key\" --env production --save\n\n# Remove a configuration value\nconfetti unset DEBUG_MODE --env production --save\n\n# Sync to GitHub\nconfetti sync-github github://owner/repo#production --env local --dry-run\n```\n\n## 🤝 Contributing\n\nWe welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.\n\n### Development Setup\n\n```bash\n# Clone the repository\ngit clone https://github.com/confetti-dev/confetti.git\ncd confetti\n\n# Install with development dependencies\npip install -e \".[dev]\"\n\n# Run tests\npytest\n\n# Run tests with coverage\npytest --cov=confetti --cov-report=html\n\n# Run linting\nruff check .\nblack --check .\nmypy confetti\n\n# Format code\nblack .\nruff --fix .\n```\n\n## 📄 License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## 🙏 Acknowledgments\n\n- Inspired by [python-dotenv](https://github.com/theskumar/python-dotenv) and [python-decouple](https://github.com/henriquebastos/python-decouple)\n- Built with modern Python best practices\n- Special thanks to all contributors\n\n## 🔗 Links\n\n- [PyPI Package](https://pypi.org/project/confetti/)\n- [GitHub Repository](https://github.com/confetti-dev/confetti)\n- [Documentation](https://confetti.readthedocs.io)\n- [Issue Tracker](https://github.com/confetti-dev/confetti/issues)\n- [Changelog](CHANGELOG.md)\n\n## 📊 Project Status\n\n- ✅ Production Ready\n- ✅ Actively Maintained\n- ✅ Semantic Versioning\n- ✅ Security Updates\n\n---\n\n\u003cp align=\"center\"\u003eMade with ❤️ by the 02Beta Team\u003c/p\u003e\n\n## Configuration File (`confetti.yaml`)\n\nConfetti supports defining configuration sources in a YAML file called `confetti.yaml`. This file can be placed in your project root directory or any parent directory, and will be automatically discovered when creating an Environment.\n\n### Basic Usage\n\nCreate a `confetti.yaml` file in your project root:\n\n```yaml\nenvironments:\n  production:\n    sources:\n      - path: ./config.yaml\n      - path: ./secrets.env\n      - uri: redis://localhost:6379\n        writable: true\n  development:\n    sources:\n      - path: ./config.dev.yaml\n      - path: ./.env.local\n```\n\nThen in your Python code:\n\n```python\nfrom confetti import Environment\n\n# Automatically loads sources from confetti.yaml for the \"production\" environment\nenv = Environment(\"production\")\nconfig = env.get_config()\n```\n\n### Merging with Explicit Sources\n\nYou can combine sources from `confetti.yaml` with explicitly provided sources:\n\n```python\n# Sources from confetti.yaml are loaded first, then these are added\nenv = Environment(\"production\", sources=[\"./override.env\"])\n```\n\n### Using Filters\n\nFilters allow you to selectively load configuration keys:\n\n```yaml\nenvironments:\n  production:\n    sources:\n      - path: ./config.yaml\n        filter:\n          include_regex: \"^(DATABASE_|REDIS_)\"  # Only load keys starting with DATABASE_ or REDIS_\n          depth: 3  # Maximum nesting depth for hierarchical data\n      - path: ./app-config.json\n        filter:\n          hierarchical_spec:  # Selectively include nested keys\n            database:\n              host: true\n              port: true\n            api:\n              endpoint: true\n```\n\n### Custom Config File Location\n\nYou can specify a custom location for the configuration file:\n\n```python\nenv = Environment(\"staging\", config_path=\"./config/my-config.yaml\")\n```\n\n## `confetti.yaml` Schema Reference\n\nThe `confetti.yaml` file follows this schema:\n\n```yaml\n# Root level - contains environments\nenvironments:\n  # Environment name (e.g., production, development, staging)\n  \u003cenvironment_name\u003e:\n    # List of configuration sources for this environment\n    sources:\n      # Each source is an object with these properties\n      - # Source location (one of these is required)\n        path: \u003cstring\u003e  # File path (relative or absolute)\n        uri: \u003cstring\u003e   # URI for remote sources (e.g., redis://...)\n        # Optional source properties\n        name: \u003cstring\u003e  # Human-readable name for the source\n        writable: \u003cboolean\u003e  # Whether this source can be written to (default: true)\n        depth: \u003cinteger\u003e  # Maximum depth for nested structure parsing\n        # Filter configuration (optional)\n        filter:\n          # Include keys matching this regex pattern\n          include_regex: \u003cstring\u003e\n\n          # Depth limit for nested structures (can also be at source level)\n          depth: \u003cinteger\u003e\n\n          # Hierarchical specification for selective inclusion\n          # Use true to include a key/path, nested objects to go deeper\n          hierarchical_spec:\n            \u003ckey\u003e: true | \u003cnested_spec\u003e\n```\n\n### Complete Example\n\nHere's a comprehensive example showing various configuration options:\n\n```yaml\nenvironments:\n  production:\n    sources:\n      # YAML configuration with regex filter\n      - path: ./config/production.yaml\n        name: \"Main Config\"\n        filter:\n          include_regex: \"^(DATABASE_|API_|CACHE_)\"\n          depth: 3\n      # JSON file with hierarchical filtering\n      - path: ./config/services.json\n        filter:\n          hierarchical_spec:\n            database:\n              primary:\n                host: true\n                port: true\n                credentials: true\n            cache:\n              redis: true\n            monitoring: false  # Exclude monitoring config\n\n      # Environment file (no filtering)\n      - path: ./.env.production\n        writable: false  # Read-only source\n\n      # Redis for dynamic configuration\n      - uri: redis://prod-redis:6379/0\n        name: \"Dynamic Config\"\n        writable: true\n      # GitHub environment (when implemented)\n      - uri: github://myorg/myrepo#production\n        name: \"GitHub Secrets\"\n        writable: false\n\n  development:\n    sources:\n      - path: ./config/development.yaml\n      - path: ./.env.local\n        writable: true\n  testing:\n    sources:\n      - path: ./config/test.yaml\n        filter:\n          include_regex: \"^TEST_\"\n      - path: ./.env.test\n```\n\n### Filter Examples\n\n#### Regex Filter\n\n```yaml\nfilter:\n  include_regex: \"^(DB_|API_)\"  # Only keys starting with DB_ or API_\n```\n\n#### Hierarchical\n\n```yaml\nfilter:\n  hierarchical_spec:\n    database:  # Include all database.* keys\n      host: true  # Include database.host\n      port: true  # Include database.port\n      pool:  # Include specific pool settings\n        size: true\n        timeout: true\n    api: true  # Include all api.* keys\n    internal: false  # Exclude all internal.* keys\n```\n\n#### Depth Limiting\n\n```yaml\nfilter:\n  depth: 2  # Only parse up to 2 levels deep\n```\n\n### Source Precedence\n\nSources are loaded in the order they appear in the configuration file. Later sources override earlier ones for the same keys:\n\n```yaml\nenvironments:\n  production:\n    sources:\n      - path: ./defaults.yaml  # Loaded first\n      - path: ./overrides.yaml  # Loaded second, overrides defaults\n      - uri: redis://localhost:6379  # Loaded last, highest precedence\n```\n\n### Error Handling\n\nConfetti handles configuration errors gracefully:\n\n- **Missing file**: If `confetti.yaml` doesn't exist, the Environment works normally with explicit sources\n- **Invalid source**: If a source in `confetti.yaml` can't be loaded, a warning is printed but other sources continue loading\n- **Invalid YAML**: If `confetti.yaml` contains invalid YAML syntax, an error is raised\n- **Missing environment**: If the requested environment isn't in `confetti.yaml`, no sources are loaded from the file\n\n### Best Practices\n\n1. **Keep secrets separate**: Use different source files for secrets vs. non-sensitive configuration\n2. **Use filters**: Limit what each source exposes to reduce the attack surface\n3. **Environment-specific files**: Create separate configuration files for each environment\n4. **Source ordering**: Place default/base configurations first, overrides last\n5. **Writable sources**: Mark sources as read-only (`writable: false`) when they shouldn't be modified\n6. **Depth limits**: Use depth limits to prevent excessive nesting in hierarchical data\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmacanderson%2Fconfetti","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmacanderson%2Fconfetti","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmacanderson%2Fconfetti/lists"}