From 8690b7e7c5c9eaf390aa75e9763b5ab5c550eb80 Mon Sep 17 00:00:00 2001 From: Helio Machado <0x2b3bfa0+git@googlemail.com> Date: Sun, 17 Jan 2021 13:10:05 +0100 Subject: [PATCH] Refactor the __getitem__ and __getattr__ methods to support Mapping ABC Fixes #20 and closes #19 because now __getitem__ serves a completely different purpose: it was intended for private use between other class methods, but now it's part of the special methods of the Mapping Abstract Base Class. --- platformshconfig/config.py | 135 ++++++++++++++++++++----------------- 1 file changed, 74 insertions(+), 61 deletions(-) diff --git a/platformshconfig/config.py b/platformshconfig/config.py index 58fb748..b63d7eb 100644 --- a/platformshconfig/config.py +++ b/platformshconfig/config.py @@ -92,7 +92,7 @@ class Config: """ A local copy of all environment variables as of when the object was initialized. """ - _environmentVariables = [] + _environmentVariables = {} """ The vendor prefix for all environment variables we care about. @@ -139,21 +139,21 @@ def __init__(self, environment_variables=None, env_prefix='PLATFORM_'): self._environmentVariables = os.environ if environment_variables is None else environment_variables self._envPrefix = env_prefix - if self['ROUTES']: - routes = self['ROUTES'] + if self._environmentVariables.get(self._envPrefix + 'ROUTES'): + routes = self._environmentVariables.get(self._envPrefix + 'ROUTES') # FIXME: Use PEP 572 on 3.8+ self._routesDef = self.decode(routes) - if self['RELATIONSHIPS']: - relationships = self['RELATIONSHIPS'] + if self._environmentVariables.get(self._envPrefix + 'RELATIONSHIPS'): + relationships = self._environmentVariables.get(self._envPrefix + 'RELATIONSHIPS') self._relationshipsDef = self.decode(relationships) self.register_formatter('pymongo', pymongo_formatter) self.register_formatter('pysolr', pysolr_formatter) self.register_formatter('postgresql_dsn', posgresql_dsn_formatter) - if self['VARIABLES']: - variables = self['VARIABLES'] + if self._environmentVariables.get(self._envPrefix + 'VARIABLES'): + variables = self._environmentVariables.get(self._envPrefix + 'VARIABLES') self._variablesDef = self.decode(variables) - if self['APPLICATION']: - application = self['APPLICATION'] + if self._environmentVariables.get(self._envPrefix + 'APPLICATION'): + application = self._environmentVariables.get(self._envPrefix + 'APPLICATION') self._applicationDef = self.decode(application) def is_valid_platform(self): @@ -164,8 +164,7 @@ def is_valid_platform(self): True if configuration can be used, False otherwise. """ - - return 'APPLICATION_NAME' in self + return self._envPrefix + 'APPLICATION_NAME' in self._environmentVariables def in_build(self): """Checks whether the code is running in a build environment. @@ -175,7 +174,7 @@ def in_build(self): """ - return self.is_valid_platform() and not self['ENVIRONMENT'] + return self.is_valid_platform() and not self._environmentVariables.get(self._envPrefix + 'ENVIRONMENT') def in_runtime(self): """Checks whether the code is running in a runtime environment. @@ -184,7 +183,7 @@ def in_runtime(self): bool: True if in a runtime environment, False otherwise. """ - return self.is_valid_platform() and self['ENVIRONMENT'] + return self.is_valid_platform() and self._environmentVariables.get(self._envPrefix + 'ENVIRONMENT') def credentials(self, relationship, index=0): """Retrieves the credentials for accessing a relationship. @@ -385,7 +384,7 @@ def on_dedicated(self): """ - return self.is_valid_platform() and self['MODE'] == 'enterprise' + return self.is_valid_platform() and self._environmentVariables.get(self._envPrefix + 'MODE') == 'enterprise' def on_enterprise(self): """Determines if the current environment is a Platform.sh Dedicated environment. @@ -419,7 +418,7 @@ def on_production(self): if not self.is_valid_platform() and not self.in_build(): return False prod_branch = 'production' if self.on_dedicated() else 'master' - return self['BRANCH'] == prod_branch + return self._environmentVariables.get(self._envPrefix + 'BRANCH') == prod_branch def register_formatter(self, name, formatter): """Adds a credential formatter to the configuration. @@ -479,19 +478,6 @@ def has_relationship(self, relationship): """ return relationship in self._relationshipsDef - def __getitem__(self, item): - """Reads an environment variable, taking the prefix into account. - - Args: - item (string): - The variable to read. - - """ - - check_name = self._envPrefix + item.upper() - - return self._environmentVariables.get(check_name) - @staticmethod def decode(variable): """Decodes a Platform.sh environment variable. @@ -516,68 +502,95 @@ def decode(variable): except json.decoder.JSONDecodeError: print('Error decoding JSON, code %d', json.decoder.JSONDecodeError) - def __contains__(self, item): - """Defines environment variable membership in Config. + def __getattr__(self, name): + """Gets a configuration property. + + Note: + Except for the raised exceptions, this method does exactly the + same as __getitem__. Args: - item (string): - variable string to be judged. + name (string): + A (magic) property name. The properties are documented in the docstring for this class. Returns: - bool: - Returns True if Config contains the variable, False otherwise. + The return types are documented in the docstring for this class. - """ + Raises: + RuntimeError: + If not running on Platform.sh, and the variable is not found. + AttributeError: + If a variable is not found, or if decoding fails. - if self[item]: - return True - return False + """ + try: + return self[name] + except KeyError: + raise AttributeError('No such variable defined: {}'.format(name)) - def __getattr__(self, config_property): + def __getitem__(self, item): """Gets a configuration property. Args: - config_property (string): - A (magic) property name. The properties are documented in the DocBlock for this class. + item (string): + A (magic) property name. The properties are documented in the docstring for this class. Returns: - The return types are documented in the DocBlock for this class. + The return types are documented in the docstring for this class. Raises: RuntimeError: If not running on Platform.sh, and the variable is not found. - AttributeError: + KeyError: If a variable is not found, or if decoding fails. """ - - is_build_var = config_property in self._directVariables.keys() - is_runtime_var = config_property in self._directVariablesRuntime.keys() - - # For now, all unprefixed variables are also runtime variables. If that ever changes this logic will change - # with it. - is_unprefixed_var = config_property in self._unPrefixedVariablesRuntime.keys() - - if is_build_var: - value = self[self._directVariables[config_property]] - elif is_runtime_var: - value = self[self._directVariablesRuntime[config_property]] - elif is_unprefixed_var: - value = self._environmentVariables.get(self._unPrefixedVariablesRuntime[config_property]) + if item in self._directVariables.keys(): + value = self._environmentVariables.get(self._envPrefix + self._directVariables[item].upper()) + elif item in self._directVariablesRuntime.keys(): + value = self._environmentVariables.get(self._envPrefix + self._directVariablesRuntime[item].upper()) + elif item in self._unPrefixedVariablesRuntime.keys(): + value = self._environmentVariables.get(self._unPrefixedVariablesRuntime[item]) else: - raise AttributeError('No such variable defined: {}'.format(config_property)) + raise KeyError('No such variable defined: {}'.format(item)) if not value: - if self.in_build() and (is_runtime_var or is_unprefixed_var): + if item not in self: raise BuildTimeVariableAccessException( - 'The {} variable is not available during build time.'.format(config_property) + 'The {} variable is not available during build time.'.format(item) ) raise NotValidPlatformException( - 'The {} variable is not defined. Are you sure you\'re running on Platform.sh?'.format(config_property) + 'The {} variable is not defined. Are you sure you\'re running on Platform.sh?'.format(item) ) return value + def __iter__(self): + """Returns an iterator over the available configuration keys. + + Returns: + iter: An iterator over the available configuration keys. + + """ + yield from self._directVariables.keys() + # Make available the runtime variables only if we aren't in a build + # environment; other variables aren't available on build time. + if not self.in_build(): + yield from self._directVariablesRuntime.keys() + # For now, all unprefixed variables are also runtime variables. + # If that ever changes this logic will change with it. + yield from self._unPrefixedVariablesRuntime.keys() + + def __len__(self): + """Returns the number of available configuration properties. + + Returns: + int: The number of available configuration properties. + + """ + return len(self.keys()) + + def pymongo_formatter(credentials): """Returns a DSN for a pymongo-MongoDB connection.