示例#1
0
def test_env(monkeypatch):
    from modconfig import Config

    # Config accepts modules path from ENV variables
    monkeypatch.setenv("MODCONFIG", "example.production")
    cfg = Config("ENV:MODCONFIG")
    assert cfg.DATABASE["host"] == "db.com"

    # Any var from config could be redefined in ENV
    monkeypatch.setenv("API_KEY", "prod_key")
    monkeypatch.setenv("DATABASE", "[1,2,3]")
    monkeypatch.setenv("SOME_LIMIT", "100")
    cfg = Config("example.production")
    assert cfg.API_KEY == "prod_key"
    assert cfg.SOME_LIMIT == 100
    # Invalid types would be ignored
    assert cfg.DATABASE == {"host": "db.com", "user": "******"}

    # Correct types would be parsed
    monkeypatch.setenv("DATABASE", '{"host": "new.com", "user": "******"}')
    cfg = Config("example.production")
    assert cfg.DATABASE == {"host": "new.com", "user": "******"}

    cfg = Config("example.defaults")
    assert cfg.DATABASE == {"host": "new.com", "user": "******"}

    monkeypatch.setenv("APP_SECRET", "value_from_env_with_prefix")
    monkeypatch.setenv("DEMOENV", "ignore_me")
    cfg = Config("example.production", config_config=dict(env_prefix="APP_"))
    assert cfg.SECRET == "value_from_env_with_prefix"
    assert cfg.ENV == "production"
示例#2
0
    def __init__(self, app: Application = None, **options):
        """Save application and create he plugin's configuration."""
        self.cfg = Config(config_config={'update_from_env': False}, **self.defaults)

        if app is not None:
            self.setup(app, **options)
        else:
            self.cfg.update_from_dict(options, exist_only=True)
示例#3
0
def test_import_modules():
    from example import tests
    from modconfig import Config

    # Config accepts modules themself
    cfg = Config(tests)
    assert cfg
    assert cfg.SECRET == "unsecure"
    assert cfg.LIMIT is None
    assert cfg.API_TOKEN_EXPIRE

    # Config accepts modules import path
    cfg = Config("example.tests", API_KEY="redefined")
    assert cfg
    assert cfg.API_KEY == "redefined"
    assert cfg.APP_DIR
    assert cfg.ENV == "tests"
    assert cfg.LIMIT is None
    assert cfg.SECRET == "unsecure"

    cfg.update_from_dict({"limit": "0.22"})
    assert cfg.LIMIT == 0.22

    mod = cfg.update_from_modules("example.unknown")
    assert not mod

    #  If the first given module is not available then next would be used.
    mod = cfg.update_from_modules(
        "example.unknown", "example.tests", "example.production"
    )
    assert mod == "example.tests"
    assert cfg.ENV == "tests"
示例#4
0
    def __init__(self, *cfg_mods: t.Union[str, ModuleType], **options):
        """Initialize the application.

        :param *cfg_mods: modules to import application's config
        :param **options: Configuration options

        """
        from .plugins import BasePlugin

        self.plugins: t.Dict[str, BasePlugin] = dict()

        # Setup the configuration
        self.cfg = Config(**self.defaults, config_config=dict(update_from_env=False))
        options['CONFIG'] = self.cfg.update_from_modules(*cfg_mods, 'env:%s' % CONFIG_ENV_VARIABLE)
        self.cfg.update(**options)
        self.cfg.update_from_env(prefix=f"{ self.cfg.name }_")

        # Setup CLI
        from .manage import Manager

        self.manage = Manager(self)

        # Setup logging
        LOG_CONFIG = self.cfg.get('LOG_CONFIG')
        if LOG_CONFIG and isinstance(LOG_CONFIG, dict) and LOG_CONFIG.get('version'):
            logging.config.dictConfig(LOG_CONFIG)  # type: ignore

        self.logger = logging.getLogger('muffin')
        self.logger.setLevel(self.cfg.LOG_LEVEL)
        self.logger.propagate = False
        if not self.logger.handlers:
            ch = logging.StreamHandler()
            ch.setFormatter(logging.Formatter(
                self.cfg.LOG_FORMAT, self.cfg.LOG_DATE_FORMAT))
            self.logger.addHandler(ch)

        super(Application, self).__init__(
            debug=self.cfg.DEBUG,
            logger=self.logger,
            trim_last_slash=self.cfg.TRIM_LAST_SLASH,
            static_folders=self.cfg.STATIC_FOLDERS,
            static_url_prefix=self.cfg.STATIC_URL_PREFIX,
        )
示例#5
0
def test_update_from_dict():
    from modconfig import Config

    cfg = Config(ignore_case=True, var1=1, VAR2=2)
    cfg.update_from_dict({"CFG_VAR1": 11, "CFG_VAR3": 33}, prefix="CFG_")
    assert cfg.var1 == 11
    assert cfg.VAR2 == 2
    with pytest.raises(AttributeError):
        assert cfg.var3

    cfg = Config(VAR=42)
    cfg.update_from_dict({"CFG_VAR": 11, "VAR": 22}, prefix="CFG_")
    assert cfg.var == 11
示例#6
0
def test_base():
    from modconfig import Config

    cfg = Config("unknown", config_config=dict(env_prefix="CONFIG"))
    assert cfg
    assert cfg._Config__env_prefix == "CONFIG"
    with pytest.raises(AttributeError):
        cfg.UNKNOWN

    cfg = Config(OPTION=42)
    assert cfg
    assert cfg.OPTION == 42
    assert cfg.get("OPTION") == 42
    assert cfg.get("UNKNOWN", True)

    cfg.update(OPTION=43)
    assert cfg.OPTION == 43

    test = cfg.update_from_modules()
    assert not test

    test = list(cfg)
    assert test == [("OPTION", 43)]

    cfg.update(fn=lambda: 42)
    assert cfg.FN() == 42
示例#7
0
文件: __init__.py 项目: klen/knocker
import logging

from modconfig import Config

__version__ = "1.2.0"

# Configuration
config: Config = Config(
    DEBUG=False,
    SCHEME='https',
    MAX_REDIRECTS=10,
    STATUS_URL='/knocker/status',
    HOSTS_ONLY=[],
    TIMEOUT=10.0,
    TIMEOUT_MAX=60.0,
    SENTRY_DSN='',
    SENTRY_FAILED_REQUESTS=False,
    RETRIES=2,
    RETRIES_MAX=10,
    RETRIES_BACKOFF_FACTOR=0.5,
    RETRIES_BACKOFF_FACTOR_MAX=600,
    LOG_FILE='-',
    LOG_LEVEL='INFO',
    LOG_FORMAT='[%(asctime)s] [%(process)s] [%(levelname)s] %(message)s',
)

# Setup logging
logger: logging.Logger = logging.getLogger('knocker')
logger.setLevel(config.LOG_LEVEL)
logger.propagate = False
if config.LOG_FILE:
    handler = logging.StreamHandler()
示例#8
0
class BasePlugin(ABC):

    """Base class for Muffin plugins."""

    app: t.Optional[Application] = None

    # Plugin options with default values
    defaults: t.Dict[str, t.Any] = {}

    # Optional middleware method
    middleware: t.Optional[t.Callable] = None

    # Optional startup method
    startup: t.Optional[t.Callable] = None

    # Optional shutdown method
    shutdown: t.Optional[t.Callable] = None

    # Optional conftest method
    conftest: t.Optional[t.Callable] = None

    @property
    @abstractmethod
    def name(self):
        """Plugin has to have a name."""
        raise NotImplementedError

    def __init__(self, app: Application = None, **options):
        """Save application and create he plugin's configuration."""
        self.cfg = Config(config_config={'update_from_env': False}, **self.defaults)

        if app is not None:
            self.setup(app, **options)
        else:
            self.cfg.update_from_dict(options, exist_only=True)

    def __repr__(self) -> str:
        """Human readable representation."""
        return f"<muffin.Plugin: { self.name }>"

    @property
    def installed(self):
        """Check the plugin is installed to an app."""
        return bool(self.app)

    def setup(self, app: Application, *, name: str = None, **options):
        """Bind app and update the plugin's configuration."""
        # allow to redefine the name for multi plugins with same type
        self.name = name or self.name  # type: ignore
        self.app = app
        self.app.plugins[self.name] = self

        # Update configuration
        self.cfg.update_from_dict(dict(app.cfg), prefix=f"{self.name}_", exist_only=True)
        self.cfg.update_from_dict(options)

        # Register a middleware
        if self.middleware:
            self.app.middleware(to_awaitable(self.middleware))

        # Bind startup
        if self.startup:
            self.app.on_startup(self.startup)

        # Bind shutdown
        if self.shutdown:
            self.app.on_shutdown(self.shutdown)
示例#9
0
class Application(BaseApp):

    """The Muffin Application."""

    # Default configuration values
    defaults: t.Dict = dict(

        # The application's name
        NAME='muffin',

        # Path to configuration module
        CONFIG=None,

        # Enable debug mode (optional)
        DEBUG=False,

        # Routing options
        TRIM_LAST_SLASH=True,

        # Static files options
        STATIC_URL_PREFIX='/static',
        STATIC_FOLDERS=[],

        # Logging options
        LOG_LEVEL='WARNING',
        LOG_FORMAT='%(asctime)s [%(process)d] [%(levelname)s] %(message)s',
        LOG_DATE_FORMAT='[%Y-%m-%d %H:%M:%S]',
        LOG_CONFIG=None,

    )

    def __init__(self, *cfg_mods: t.Union[str, ModuleType], **options):
        """Initialize the application.

        :param *cfg_mods: modules to import application's config
        :param **options: Configuration options

        """
        from .plugins import BasePlugin

        self.plugins: t.Dict[str, BasePlugin] = dict()

        # Setup the configuration
        self.cfg = Config(**self.defaults, config_config=dict(update_from_env=False))
        options['CONFIG'] = self.cfg.update_from_modules(*cfg_mods, 'env:%s' % CONFIG_ENV_VARIABLE)
        self.cfg.update(**options)
        self.cfg.update_from_env(prefix=f"{ self.cfg.name }_")

        # Setup CLI
        from .manage import Manager

        self.manage = Manager(self)

        # Setup logging
        LOG_CONFIG = self.cfg.get('LOG_CONFIG')
        if LOG_CONFIG and isinstance(LOG_CONFIG, dict) and LOG_CONFIG.get('version'):
            logging.config.dictConfig(LOG_CONFIG)  # type: ignore

        self.logger = logging.getLogger('muffin')
        self.logger.setLevel(self.cfg.LOG_LEVEL)
        self.logger.propagate = False
        if not self.logger.handlers:
            ch = logging.StreamHandler()
            ch.setFormatter(logging.Formatter(
                self.cfg.LOG_FORMAT, self.cfg.LOG_DATE_FORMAT))
            self.logger.addHandler(ch)

        super(Application, self).__init__(
            debug=self.cfg.DEBUG,
            logger=self.logger,
            trim_last_slash=self.cfg.TRIM_LAST_SLASH,
            static_folders=self.cfg.STATIC_FOLDERS,
            static_url_prefix=self.cfg.STATIC_URL_PREFIX,
        )

    def __repr__(self) -> str:
        """Human readable representation."""
        return f"<muffin.Application: { self.cfg.name }>"

    def import_submodules(self, *submodules: str) -> t.Dict[str, ModuleType]:
        """Import application components."""
        parent_frame = inspect.stack()[1][0]
        package_name = parent_frame.f_locals['__name__']
        return import_submodules(package_name, *submodules)