diff --git a/.gitignore b/.gitignore index 82ba1ba..5f62e89 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,9 @@ docs/_build/ # Autoenv .env + +# GAE +google +google/ +google_appengine/ +google_appengine_*.zip diff --git a/.travis.yml b/.travis.yml index fbbe4a5..d6b9da8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,8 +10,8 @@ install: # environment dependencies before_script: - - curl -O https://storage.googleapis.com/appengine-sdks/featured/google_appengine_1.9.23.zip - - unzip -q google_appengine_1.9.23.zip + - curl -O https://storage.googleapis.com/appengine-sdks/featured/google_appengine_1.9.27.zip + - unzip -q google_appengine_1.9.27.zip - ln -s google_appengine/google google # command to run tests diff --git a/CHANGELOG.md b/CHANGELOG.md index 7294593..ed407b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +0.2.0 +==== +- Added the `environ` module. + + 0.1.2 ===== - Actually fix the MANIFEST.in contents. diff --git a/README.md b/README.md index a7ada69..05f55b1 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,13 @@ GAEK: Google App Engine Kit =============================== -.. image:: https://img.shields.io/travis/erichiggins/gaek.svg - :target: https://travis-ci.org/erichiggins/gaek - -.. image:: https://img.shields.io/pypi/v/gaek.svg - :target: https://pypi.python.org/pypi/gaek +[![Build Status](https://travis-ci.org/erichiggins/gaek.svg)](https://travis-ci.org/erichiggins/gaek) A collection of useful tools for Python apps running on Google App Engine. * Free software: BSD license -* Documentation: https://gaek.readthedocs.org. +* Documentation: http://erichiggins.github.io/gaek/ NDB JSON module --------------- @@ -29,8 +25,40 @@ Usage: Feature parity with the Python `json` module functions. -`ndb_json.dumps` +* `ndb_json.dumps` +* `ndb_json.dump` +* `ndb_json.loads` + + +Environment module +------------------ + +* `environ.get_dot_target_name(version=None, module=None)` + + Returns the current version/module in `-dot-` notation which is used by `target:` parameters. + +* `environ.get_environ_dict()` + + Return a dictionary of all environment keys/values. + +* `environ.is_host_google()` + + True if the app is being hosted from Google App Engine servers. + +* `environ.is_development()` + + True if the dev_appserver is running (localhost or local development server). + +* `environ.is_staging(version=None)` + + True if the app is hosted by Google (appspot.com) but the version is not the default. + +* `environ.is_production(version=None)` + + True if the app is being hosted by Google and the default version. + +* `environ.is_default_version(version=None)` + + True if the current or specified app version is the default. -`ndb_json.dump` -`ndb_json.loads` diff --git a/VERSION b/VERSION index d917d3e..0ea3a94 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1.2 +0.2.0 diff --git a/gaek/__init__.py b/gaek/__init__.py index 7c9cc1a..9f343ab 100755 --- a/gaek/__init__.py +++ b/gaek/__init__.py @@ -2,4 +2,4 @@ __author__ = 'Eric Higgins' __email__ = 'erichiggins@gmail.com' -__version__ = '0.1.2' +__version__ = '0.2.0' diff --git a/gaek/environ.py b/gaek/environ.py new file mode 100644 index 0000000..1f04e0a --- /dev/null +++ b/gaek/environ.py @@ -0,0 +1,181 @@ +# -*- coding: utf-8 -*- +""" +Environment discovery and helper functions for Google App Engine. +Some methods from the following modules have been made available for convenience: + +* [`google.appengine.api.app_identity`](https://cloud.google.com/appengine/docs/python/appidentity/) +* [`google.appengine.api.modules`](https://cloud.google.com/appengine/docs/python/modules/) +* [`google.appengine.api.namespace_manager`](https://cloud.google.com/appengine/docs/python/multitenancy/) + +Example: + + import environ + + # Only send emails in production. + if environ.is_production(): + mail.send(*args, **kwargs) + +""" + +__author__ = 'Eric Higgins' +__copyright__ = 'Copyright 2015, Eric Higgins' +__version__ = '0.0.1' +__email__ = 'erichiggins@gmail.com' + + +import os + +from google.appengine.api import app_identity +from google.appengine.api import modules +from google.appengine.api import namespace_manager + + +__all__ = ( + # App Identity functions. + 'get_application_id', + 'get_default_version_hostname', + 'get_service_account_name', + # Module functions. + 'get_current_instance_id', + 'get_current_module_name', + 'get_current_version_name', + 'get_default_version', + 'get_hostname', + 'get_modules', + 'get_versions', + # Namespace functions. + 'get_namespace', + 'google_apps_namespace', + # Helper functions. + 'get_dot_target_name', + 'get_environ_dict', + 'is_host_google', + 'is_development', + 'is_staging', + 'is_production', + 'is_default_version', +) + + +_UNDEFINED = '_UNDEFINED_' + + +# App Identity functions. +get_application_id = app_identity.get_application_id +get_default_version_hostname = app_identity.get_default_version_hostname +get_service_account_name = app_identity.get_service_account_name + + +# Module functions. +get_current_instance_id = modules.get_current_instance_id +get_current_module_name = modules.get_current_module_name +get_current_version_name = modules.get_current_version_name +get_default_version = modules.get_default_version +get_hostname = modules.get_hostname +get_modules = modules.get_modules +get_versions = modules.get_versions + + +# Namespace functions. +get_namespace = namespace_manager.get_namespace +google_apps_namespace = namespace_manager.google_apps_namespace + + +# Helper functions. + + +def is_host_google(): + """True if the app is being hosted from Google App Engine servers.""" + return os.environ.get('SERVER_SOFTWARE', '').startswith('Google') or get_hostname().endswith('.appspot.com') + + +def is_default_version(version=None): + """True if the current or specified app version is the default.""" + version = version or get_current_version_name() + return get_current_version_name() == 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') + + +def is_staging(version=None): + """True if the app is hosted by Google (appspot.com) but the version is not the default.""" + return is_host_google() and not is_default_version(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 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() + module = module or get_current_module_name() + return '-dot-'.join((version, module)) + + +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} + + +def _get_app_identity_dict(keys): + """Return a dictionary of key/values from the app_identity module functions.""" + return {k: getattr(app_identity, k)() for k in keys} + + +def _get_modules_dict(keys): + """Return a dictionary of key/values from the modules module functions.""" + return {k: getattr(modules, k)() for k in keys} + + +def _get_namespace_manager_dict(keys): + """Return a dictionary of key/values from the namespace_manager module functions.""" + return {k: getattr(namespace_manager, k)() for k in keys} + + +def get_environ_dict(): + """Return a dictionary of all environment keys/values.""" + return { + 'os.environ': _get_os_environ_dict(( + 'AUTH_DOMAIN', + 'CURRENT_CONFIGURATION_VERSION', + 'CURRENT_MODULE_ID', + 'CURRENT_VERSION_ID', + 'DEFAULT_VERSION_HOSTNAME', + 'FEDERATED_IDENTITY', + 'FEDERATED_PROVIDER', + 'GAE_LOCAL_VM_RUNTIME', + 'HTTP_HOST', + 'HTTP_PROXY', + 'HTTP_X_APPENGINE_HTTPS', + 'HTTP_X_APPENGINE_QUEUENAME', + 'HTTP_X_ORIGINAL_HOST', + 'HTTP_X_ORIGINAL_SCHEME', + 'SERVER_NAME', + 'SERVER_PORT', + 'SERVER_SOFTWARE', + 'USER_IS_ADMIN', + )), + 'app_identity': _get_app_identity_dict(( + 'get_service_account_name', + 'get_application_id', + 'get_default_version_hostname', + )), + 'modules': _get_modules_dict(( + 'get_current_module_name', + 'get_current_version_name', + 'get_current_instance_id', + 'get_modules', + 'get_versions', + 'get_default_version', + 'get_hostname', + )), + 'namespace_manager': _get_namespace_manager_dict(( + 'get_namespace', + 'google_apps_namespace', + )), + } diff --git a/tests/test_environ.py b/tests/test_environ.py new file mode 100755 index 0000000..968bc70 --- /dev/null +++ b/tests/test_environ.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +test_environ +---------------------------------- + +Tests for `environ` module. +""" + +import os +import unittest + +from google.appengine.api import app_identity +from google.appengine.api import modules +from google.appengine.api import namespace_manager +from google.appengine.ext import testbed + +from gaek import environ + + +class TestEnviron(unittest.TestCase): + + def setUp(self): + # Setups app engine test bed. + # http://code.google.com/appengine/docs/python/tools/localunittesting.html + self.testbed = testbed.Testbed() + self.testbed.activate() + # Declare which service stubs you want to use. + self.testbed.init_app_identity_stub() + self.testbed.init_modules_stub() + + def tearDown(self): + self.testbed.deactivate() + + def test_app_identity_functions(self): + assert app_identity.get_application_id == environ.get_application_id + assert app_identity.get_default_version_hostname == environ.get_default_version_hostname + assert app_identity.get_service_account_name == environ.get_service_account_name + + def test_modules_functions(self): + assert modules.get_current_instance_id == environ.get_current_instance_id + assert modules.get_current_module_name == environ.get_current_module_name + assert modules.get_current_version_name == environ.get_current_version_name + assert modules.get_default_version == environ.get_default_version + assert modules.get_hostname == environ.get_hostname + assert modules.get_modules == environ.get_modules + assert modules.get_versions == environ.get_versions + + def test_namespace_functions(self): + assert namespace_manager.get_namespace == environ.get_namespace + assert namespace_manager.google_apps_namespace == environ.google_apps_namespace + + def test_get_environ_dict(self): + # TODO(eric): This one is a bit hefty. + pass + + def test_get_dot_target_name(self): + val = environ.get_dot_target_name() + assert val == 'testbed-version-dot-default', repr(val) + + def test_is_host_google(self): + val = environ.is_host_google() + assert val == False, repr(val) + + def test_is_development(self): + val = environ.is_development() + assert val == True, repr(val) + + def test_is_staging(self): + val = environ.is_staging() + assert val == False, repr(val) + + def test_is_production(self): + val = environ.is_production() + assert val == False, repr(val) + + def test_is_default_version(self): + val = environ.is_default_version() + assert val == False, repr(val) + + +if __name__ == '__main__': + unittest.main()