Skip to main content

🎉 PyConfetti 🎉

Python 3.11+ License: Unlicense

"Because config files should be as fun as throwing confetti!" 🎊

A pure Python parser for the Confetti configuration language - a simple, flexible, and expressive configuration syntax that makes your config files look like a party! 🥳

✨ Features

  • 🚀 Zero dependencies - just plain Python!
  • 🧰 uv compatible - works perfectly with modern Python tooling!
  • 🔍 Full parsing support for directives, arguments, subdirectives, and comments
  • 🛡️ Robust error handling with helpful error messages
  • 🧩 Two convenient APIs: parse and walk
  • 🔄 Bidirectional support for reading AND writing configs
  • 📝 Triple-quoted strings for multiline values
  • 💪 Well-typed with complete type annotations

🚀 Installation

uv pip install pyconfetti

# Or install from source
git clone https://github.com/yourusername/pyconfetti.git
cd pyconfetti
uv pip install -e .

🎯 Quick Start

Parsing a Confetti Configuration

from pyconfetti import parse, pretty_print

# Example configuration
config = """
# This is a comment
server {
    host localhost
    port 8080

    ssl {
        enabled true
        cert "/path/to/cert.pem"
    }
}
"""

# Parse the configuration
unit = parse(config)

# Access the parsed data
server_directive = unit.root.subdirectives[0]

# Iterate through arguments in subdirectives
host_value = None
port_value = None

for subdir in server_directive.subdirectives:
    for i, arg in enumerate(subdir.arguments):
        if arg.value == "host" and i+1 < len(subdir.arguments):
            host_value = subdir.arguments[i+1].value  # "localhost"
        if arg.value == "port" and i+1 < len(subdir.arguments):
            port_value = subdir.arguments[i+1].value  # "8080"

print(f"Host: {host_value}, Port: {port_value}")

# Pretty print the parsed configuration
pretty_print(unit)

Walking Through a Configuration

from pyconfetti import walk, ElementType

def callback(element_type, arguments, comment):
    if element_type == ElementType.COMMENT:
        print(f"Found comment: {comment.text}")
    elif element_type == ElementType.DIRECTIVE:
        print(f"Found directive with arguments: {[arg.value for arg in arguments]}")
    elif element_type == ElementType.BLOCK_ENTER:
        print("Entering block")
    elif element_type == ElementType.BLOCK_LEAVE:
        print("Leaving block")
    return True  # Continue walking

# Walk through the configuration
walk(config, callback)

🎭 Why Confetti?

Confetti is a configuration language that looks like NGINX config but is more flexible. Based on the original C implementation, this pure Python parser offers full compatibility with the Confetti specification. It's perfect when:

  • YAML is too space-sensitive for you 😬
  • JSON doesn't support comments 🙄
  • TOML feels too INI-like 😴
  • XML makes you want to cry 😭

Confetti strikes the perfect balance between readability, expressiveness, and simplicity! ✨

🧩 Syntax Example

For more detailed information about the Confetti syntax, check out the official Confetti specification

# This is a comment

# Basic directives
server_name "my-awesome-app.com";
port 8080;

# Blocks with subdirectives
database {
    url "postgres://user:password@localhost:5432/mydb"
    pool_size 10
    timeout 30
}

# Multiple arguments
allowed_origins "http://localhost:3000" "https://example.com";

# Triple-quoted strings for multiline content
description """
    This is a multi-line description
    that spans several lines
    without needing escapes
""";

# Nested blocks
logging {
    level "info"

    file {
        path "/var/log/app.log"
        rotate true
        max_size "10MB"
    }
}

🗺️ Using the Mapper

PyConfetti's mapper module provides a simple way to map between Confetti configurations and Python dataclasses:

from typing import Optional
from pyconfetti import confetti, load_confetti, dump_confetti

# Define your classes with type annotations
@confetti
class Database:
    host: str
    port: int
    username: Optional[str] = None
    password: Optional[str] = None

@confetti
class WebServer:
    host: str
    dbname: str
    port: int = 8080

@confetti
class Config:
    database: Database
    server: WebServer

# Example Confetti configuration
config_text = """
config {
    database {
        host localhost
        port 5432
        username admin
    }

    server {
        host 127.0.0.1
        dbname myapp
    }
}
"""

# Load the configuration into a Config object
config = load_confetti(config_text, Config)

# Access the configuration as regular Python objects
print(f"Database: {config.database.host}:{config.database.port}")
print(f"Server: {config.server.host}:{config.server.port}")

# Create a new configuration programmatically
new_config = Config(
    database=Database(host="localhost", port=5433, username="postgres"),
    server=WebServer(host="0.0.0.0", dbname="newapp", port=9000),
)

# Convert back to Confetti
new_config_text = dump_confetti(new_config)

🛠️ Advanced Usage

Custom Configuration Options

from pyconfetti import parse, ConfettiOptions

# Create custom options
options = ConfettiOptions(
    max_depth=50,               # Maximum nesting depth
    allow_bidi=True,            # Allow bidirectional Unicode control characters
    c_style_comments=True,      # Support C-style comments (/* ... */)
    expression_arguments=True   # Enable expression evaluation in arguments
)

# Parse with custom options
unit = parse(config_text, options)

Building Configurations Programmatically

from pyconfetti import Directive, Argument, ConfettiUnit, pretty_print

# Create directives and arguments
server = Directive()
server.arguments.append(Argument(value="server", offset=0, length=6))

host = Directive()
host.arguments.append(Argument(value="host", offset=0, length=4))
host.arguments.append(Argument(value="localhost", offset=0, length=9))

port = Directive()
port.arguments.append(Argument(value="port", offset=0, length=4))
port.arguments.append(Argument(value="8080", offset=0, length=4))

# Add subdirectives
server.subdirectives.extend([host, port])

# Create a configuration unit
unit = ConfettiUnit()
unit.root.subdirectives.append(server)

# Pretty print the configuration
pretty_print(unit)

🔍 Error Handling

PyConfetti provides detailed error messages to help you debug configuration issues:

from pyconfetti import parse, ConfettiError

try:
    unit = parse(invalid_config)
except ConfettiError as e:
    print(f"Configuration error: {e}")
    # Handle the error appropriately

💻 Development

PyConfetti has no runtime dependencies, so you can start developing right away!

# Clone the repository
git clone https://github.com/yourusername/pyconfetti.git
cd pyconfetti

# Install development dependencies
uv pip install -e ".[dev]"

# Run tests
python run_test_suite.py

🤝 Contributing

Contributions are welcome! Feel free to:

  • 🐛 Report bugs
  • 💡 Suggest features
  • 🔧 Submit pull requests
  • 📖 Improve documentation

For questions and discussions about the Confetti format itself, please visit the original Confetti repository.

📄 License

This project is licensed under the Unlicense - dedicated to the public domain. See the LICENSE file for details.


Made with ❤️ and a handful of confetti! 🎊

Metadata

Release files for pyconfetti 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pyconfetti 0.1.0
File Size Uploaded Provenance Actions
pyconfetti-0.1.0.tar.gz 14.8 kB Without provenance Details for pyconfetti-0.1.0.tar.gz

Release files / pyconfetti-0.1.0.tar.gz

Download URL pyconfetti-0.1.0.tar.gz
Size 14.8 kB
Tags Source
SHA-256 checksum
How to use checksums
ec3efc8639027b42a2272adb3d9bd7f0ab07616dd2befe0e988af5fc4bdb869b
BLAKE2b-256 checksum
How to use checksums
80517f5a2704cbe80f36b605c766f2a66d95080a93a7b82bdeb211a7c30d07e5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.6.10

Security for pyconfetti 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

1 release file No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page