From f3427e4fd4c09b2dc632b535aa39dfc4fbd50180 Mon Sep 17 00:00:00 2001 From: Eric Higgins Date: Thu, 7 Dec 2017 15:46:46 -0800 Subject: [PATCH] Release 0.4.0 (#18) * Added support for JSON encoding of subclasses of supported types * Sorted the ndb types for deterministic iteration * Add a flag fetch_models to specify the way to encode NDB keys (#11) * Add a flag fetch_models to specify the way to encode NDB keys * Keep backward compatibility * Introduced flags ndb_keys_as_entities, ndb_keys_as_pairs, ndb_keys_as_urlsafe * Covered with unit tests * Rebased with the latest development branch and simplified the code * Fixed documentation and test dependencies * Fixing the key error * Add a test that is_default_version() returns False * Fix get_current_module_name * All done I think * Cleaning things up a bit * fix typo * remove whitespace * Bump version to 0.4.0 for release. --- CHANGELOG.md | 4 +++ README.md | 22 ++++++++++++++ VERSION | 2 +- gaek/__init__.py | 2 +- gaek/environ.py | 57 +++++++++++++++++++++++++++++++++++ tests/test_environ.py | 70 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 155 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d3b555..939a49d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +0.4.0 +===== +- Added `_safe` methods for functions which break outside of the GAE environment. + 0.3.0 ===== - Added support for encoding `ndb.Key` objects as entities, pairs, or urlsafe-strings. diff --git a/README.md b/README.md index 01dc2ea..0a50d14 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,10 @@ Environment module Returns the current version/module in `-dot-` notation which is used by `target:` parameters. +* `environ.get_dot_target_name_safe(version=None, module=None)` + + Same as `environ.get_dot_target_name`, but this function returns `None` if there is no version or module found. + * `environ.get_environ_dict()` Return a dictionary of all environment keys/values. @@ -116,12 +120,30 @@ Environment module True if the app is hosted by Google (appspot.com) but the version is not the default. +* `environ.is_staging_safe(version=None)` + + Same as `environ.is_staging`, but returns `None` if there is no version found. + * `environ.is_production(version=None)` True if the app is being hosted by Google and the default version. +* `environ.is_production_safe(version=None)` + + Same as `environ.is_production`, but returns `None` if there is no version found. + * `environ.is_default_version(version=None)` True if the current or specified app version is the default. +* `environ.is_default_version_safe(version=None)` + + Same as `environ.is_default_version`, but returns `None` if there is no version found. + +* `environ.get_current_version_name_safe()` + + Wrapper around `google.appengine.api.modules.get_current_version_name`. Returns `None` if there is any error raised, otherwise it returns the current version name. + +* `environ.get_current_module_name_safe()` + Wrapper around `google.appengine.api.modules.get_current_module_name`. Returns `None` if there is any error raised, otherwise it returns the current version name. diff --git a/VERSION b/VERSION index 0d91a54..1d0ba9e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.3.0 +0.4.0 diff --git a/gaek/__init__.py b/gaek/__init__.py index d5c7773..b478428 100755 --- a/gaek/__init__.py +++ b/gaek/__init__.py @@ -2,4 +2,4 @@ __author__ = 'Eric Higgins' __email__ = 'erichiggins@gmail.com' -__version__ = '0.3.0' +__version__ = '0.4.0' diff --git a/gaek/environ.py b/gaek/environ.py index 754ddc7..2c65a18 100644 --- a/gaek/environ.py +++ b/gaek/environ.py @@ -50,12 +50,18 @@ 'google_apps_namespace', # Helper functions. 'get_dot_target_name', + 'get_dot_target_name_safe', 'get_environ_dict', 'is_host_google', 'is_development', 'is_staging', + 'is_staging_safe', 'is_production', + 'is_production_safe', 'is_default_version', + 'is_default_version_safe', + 'get_current_module_name_safe', + 'get_current_version_name_safe' ) @@ -84,6 +90,20 @@ # Helper functions. +def get_current_version_name_safe(): + """Returns the current version of the app, or None if there is no current version found.""" + try: + return modules.get_current_version_name() + except KeyError: + return None + + +def get_current_module_name_safe(): + """Returns the current module of the app, or None if there is no current module found..""" + try: + return modules.get_current_module_name() + except KeyError: + return None def is_host_google(): @@ -97,6 +117,15 @@ def is_default_version(version=None): return version == get_default_version() +def is_default_version_safe(version=None): + """ + True if the current or specified app version is the default. + Returns False when there is no version found. + """ + version = version or get_current_version_name_safe() + return version == get_default_version() + + def is_development(): """True if the dev_appserver is running (localhost or local development server).""" return os.environ.get('SERVER_SOFTWARE', '').startswith('Development') @@ -107,11 +136,27 @@ def is_staging(version=None): return is_host_google() and not is_default_version(version) +def is_staging_safe(version=None): + """True if the app is hosted by Google (appspot.com) but the version is not the default.""" + is_default_version = is_default_version_safe() + if is_default_version is None: + return False + return is_host_google() and not is_default_version + + def is_production(version=None): """True if the app is being hosted by Google and the default version.""" return is_host_google() and is_default_version(version) +def is_production_safe(version=None): + """True if the app is being hosted by Google and the default version.""" + is_default_version = is_default_version_safe(version) + if is_default_version is None: + return False + return is_host_google() and is_default_version + + def get_dot_target_name(version=None, module=None): """Returns the current version/module in -dot- notation which is used by `target:` parameters.""" version = version or get_current_version_name() @@ -119,6 +164,18 @@ def get_dot_target_name(version=None, module=None): return '-dot-'.join((version, module)) +def get_dot_target_name_safe(version=None, module=None): + """ + Returns the current version/module in -dot- notation which is used by `target:` parameters. + If there is no current version or module then None is returned. + """ + version = version or get_current_version_name_safe() + module = module or get_current_module_name_safe() + if version and module: + return '-dot-'.join((version, module)) + return None + + def _get_os_environ_dict(keys): """Return a dictionary of key/values from os.environ.""" return {k: os.environ.get(k, _UNDEFINED) for k in keys} diff --git a/tests/test_environ.py b/tests/test_environ.py index 122a183..7e6b26c 100644 --- a/tests/test_environ.py +++ b/tests/test_environ.py @@ -7,6 +7,7 @@ Tests for `environ` module. """ +import mock import os import unittest @@ -58,6 +59,16 @@ def test_get_dot_target_name(self): val = environ.get_dot_target_name() assert val == 'testbed-version-dot-default', repr(val) + def test_get_dot_target_name_safe(self): + with mock.patch('gaek.environ.get_current_version_name_safe', return_value=None): + val = environ.get_dot_target_name_safe() + assert val is None, val + with mock.patch('gaek.environ.get_current_module_name_safe', return_value=None): + val = environ.get_dot_target_name_safe() + assert val is None, val + val = environ.get_dot_target_name_safe() + assert val == 'testbed-version-dot-default', repr(val) + def test_is_host_google(self): val = environ.is_host_google() assert val == False, repr(val) @@ -70,14 +81,73 @@ def test_is_staging(self): val = environ.is_staging() assert val == False, repr(val) + def test_is_staging_safe(self): + with mock.patch('gaek.environ.get_current_version_name_safe', return_value=None): + val = environ.is_staging_safe() + assert val is False, repr(val) + val = environ.is_staging_safe() + assert val == False, repr(val) + def test_is_production(self): val = environ.is_production() assert val == False, repr(val) + def test_is_production_safe(self): + with mock.patch('gaek.environ.get_current_version_name_safe', return_value=None): + val = environ.is_production_safe() + assert val is False, repr(val) + val = environ.is_production_safe() + assert val == False, repr(val) + def test_is_default_version(self): val = environ.is_default_version() assert val == False, repr(val) + def test_is_default_version_safe(self): + with mock.patch('gaek.environ.get_current_version_name_safe', return_value=None): + val = environ.is_default_version_safe() + assert val == False, repr(val) + + def test_get_current_version_name_safe(self): + # The version is stored in an environment variable 'CURRENT_VERSION_ID'. + # If that variable isn't present then an error will be raised unless we catch it. + saved_version = os.environ.pop('CURRENT_VERSION_ID', None) + + version = 'v1' + try: + version = environ.get_current_version_name_safe() + except Exception as err: + self.fail('Unexpected exception when getting current version: {}'.format( + err.message)) + assert version is None + + os.environ['CURRENT_VERSION_ID'] = saved_version + + # Now the environment variable is back. + version = environ.get_current_version_name_safe() + assert 'testbed-version' == version, version + + def test_get_current_module_name_safe(self): + """ + Test that environ.get_current_module_name returns None when there is no + current module, rather than raising an error. + """ + # The current module is stored in an environment variable 'CURRENT_MODULE_ID'. + saved_module_name = os.environ.pop('CURRENT_MODULE_ID', None) + + current_module = 'v1-app' + try: + current_module = environ.get_current_module_name_safe() + except Exception as err: + self.fail('Unexpected exception when getting current module: {}'.format( + err.message)) + assert current_module is None + + os.environ['CURRENT_MODULE_ID'] = saved_module_name + # Now the environment variable is back. + current_module = environ.get_current_module_name_safe() + assert 'default' == current_module, current_module + if __name__ == '__main__': unittest.main()