Integration Examples

Flask

Flask is a microframework for Python web applications. It’s flexible and extensible. Although there are best practices and traditions, Flask doesn’t really enforce the only one way to do it.

If you are working on a small project the chances are that you are using some Flask extensions like Flask-Mail or Flask-WTF. The extensions of the past are often got configured through environment variables only. It makes the use of Piny cumbersome. In mid-sized and large Flask projects though, you usually avoid using extra dependencies whenever possible. In such a case you can fit your code to use Piny pretty easy.

Here is an example of a simple Flask application. Configuration file is loaded with Piny and validated with Pydantic.

from flask import Flask
from flask.logging import default_handler
from piny import YamlLoader, StrictMatcher, PydanticV2Validator
from pydantic import BaseModel, validator
from typing import Any, Dict, Optional
from werkzeug.serving import run_simple
import logging
import sys

# Watch out!
# Pydantic V2 deprecated some model's methods:
# https://docs.pydantic.dev/2.0/migration/
#
# For Pydantic v2 use `PydanticV2Validator`
# For Pydantic v1 use `PydanticValidator`


#
# Validation
#


class AppSettings(BaseModel):
    company: str
    secret: str
    max_content_len: Optional[int] = None
    debug: bool = False
    testing: bool = False


class LoggingSettings(BaseModel):
    fmt: str
    date_fmt: str
    level: str

    @validator("level")
    def validate_name(cls, value):
        upper = value.upper()
        if upper not in logging._nameToLevel:
            raise ValueError("Invalid logging level")
        return upper


class Configuration(BaseModel):
    app: AppSettings
    logging: LoggingSettings


#
# Helpers
#


def configure_app(app: Flask, configuration: Dict[str, Any]) -> None:
    """
    Apply configs to application
    """
    app.settings = configuration
    app.secret_key = app.settings["app"]["secret"].encode("utf-8")


def configure_logging(app: Flask) -> None:
    """
    Configure app's logging
    """
    app.logger.removeHandler(default_handler)
    log_formatter = logging.Formatter(
        fmt=app.settings["logging"]["fmt"], datefmt=app.settings["logging"]["date_fmt"]
    )
    log_handler = logging.StreamHandler()
    log_handler.setFormatter(log_formatter)
    log_handler.setLevel(app.settings["logging"]["level"])
    app.logger.addHandler(log_handler)


#
# Factory
#


def create_app(path: str) -> Flask:
    """
    Application factory
    """
    # Get and validate config
    config = YamlLoader(
        path=path,
        matcher=StrictMatcher,
        validator=PydanticV2Validator,
        schema=Configuration,
    ).load()

    # Initialize app
    app = Flask(__name__)

    # Configure app
    configure_app(app, config)
    configure_logging(app)

    return app


if __name__ == "__main__":
    app = create_app(sys.argv[1])

    @app.route("/")
    def hello():
        return "Hello World!"

    # Run application:
    # $ python flask_integration.py your-config.yaml
    run_simple(hostname="localhost", port=5000, application=app)

You can use the same pattern with application factory in other frameworks, like aiohttp or sanic.

Command line

There are many possible applications for Piny CLI utility. For example, you can use it for Kubernetes deployment automation in your CI/CD pipeline.

Piny command line tool works both with standard input/output and files.

Standard input and output

$ export PASSWORD=mySecretPassword
$ echo "db: \${PASSWORD}" | piny
db: mySecretPassword

Files

$ piny config.template config.yaml

Or you can substitute environment variables in place:

$ piny production.yaml production.yaml