Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A way to specify required environment variables #3

Open
okke-formsma opened this issue Oct 24, 2017 · 5 comments
Open

A way to specify required environment variables #3

okke-formsma opened this issue Oct 24, 2017 · 5 comments

Comments

@okke-formsma
Copy link

It would be nice to have a way to specify which environment variables are required to be set, so the flask app will quit early when there are environment variables missing.

@brettlangdon
Copy link
Owner

@okke-formsma thanks for the suggestion!

Do you have an idea on how this will work? Is there a special way you define certain values that are required?

I think this is a good idea, I am trying to think about how it plays with #2, since that says "if you didn't predefine a setting, then it won't get loaded".

@okke-formsma
Copy link
Author

@brettlangdon, I was thinking along the lines of setting the required fields to a specific value, something like

from flask_env import RequiredEnvironmentValue
class StagingConfig(metaclass=MetaFlaskEnv):
    ENV_PREFIX = 'FLASK_'
    SECRET_KEY = RequiredEnvironmentValue
    ELASTICSEARCH_HOST = RequiredEnvironmentValue
    SQLALCHEMY_DATABASE_URI = RequiredEnvironmentValue

@brettlangdon
Copy link
Owner

Alright, I just had a idea... feel free to tell me I am crazy or complicating things. I was trying to think about other use cases, like mapping env variables to different names, or explicitly casting values to a specific type, or making certain ones required, etc etc. So I came up with the following idea/solution.

from flask_env import MetaFlaskEnv, Loader

class Settings(metaclass=MetaFlaskEnv):
    # This value is required, raise exception if it is not provided
    SECRET_KEY = Loader.required()
    
    # Load this value from the `DB_URI` env variable, and it is required to be set
    SQLALCHEMY_DATABASE_URI = Loader.name('DB_URI').required()

    # Load this value from `PORT`, default to `8000`, and explicitly cast the value to an `int`
    PORT = Loader.default(8000).cast(int)

    # Can still assign defaults like normal
    DEBUG = True
    Elasticsearch_HOST = 'localhost'    

@okke-formsma
Copy link
Author

okke-formsma commented Oct 25, 2017

That's nice. I'm not a very big fan of the chained commands. Maybe just use optional parameters instead? Also, I think variables should be implicitly required, unless a default value is provided.

from flask_env import MetaFlaskEnv, EnvLoader

class Settings(metaclass=MetaFlaskEnv):
	# This value is required, raise exception if it is not provided
	SECRET_KEY = EnvLoader()
	
	# Load this value from the `DB_URI` env variable, and it is required to be set
	SQLALCHEMY_DATABASE_URI = EnvLoader(name='DB_URI')

	# Load this value from `PORT`, default to `8000`, and explicitly cast the value to an `int`
	PORT = EnvLoader(default=8000, cast=int)

	# Can still assign defaults like normal
	DEBUG = True
	Elasticsearch_HOST = 'localhost'  

@okke-formsma
Copy link
Author

okke-formsma commented Nov 13, 2017

I gave this a stab for myself, feel free to use it in any way.

This is based on the descriptor pattern (https://docs.python.org/2/howto/descriptor.html)

class EnvVar():
    def __init__(self, name=None, default=None, cast=None):
        """ Load environment name `name`. Use default value `default`. Raises an ValueError if no environment variable
        is found and no default is set.  Will cast automatically to the type `cast` or to the type of `default` if not 
        set.
        
        Example:

            DevelopmentConfig():
                DEBUG = EnvVar('FLASK_DEBUG', default=False)
                PROFILE_RATE = EnvVar(cast=float)
                SQLALCHEMY_DATABASE_URI = EnvVar('DB')
        """
        self.name = name
        self.default = default
        if cast is None:
            self.cast = type(default)
        else:
            self.cast = cast
        self.value = None

    def __get__(self, obj, objtype=None):
        value = os.getenv(self.name)
        if value is None:
            if self.default is None:
                raise ValueError("Environment variable `{}` was expected to be set.".format(self.name))
            else:
                return self.default
        
        if self.cast is None:
            return value

        return self.cast(value)

    def __set__(self, obj, value):
        raise ValueError("This variable can not be set but will be initiated from environment values.")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants