From 6944c1780dedcdd7f85415d97099233b64223d80 Mon Sep 17 00:00:00 2001 From: Noah Zoschke Date: Tue, 17 May 2011 18:24:05 -0700 Subject: [PATCH] new python langpack playground; ast aware settings.py code injection --- .gitignore | 1 + Readme.md | 18 ++++++ opt/dbs.py | 31 ++++++++++ opt/inject_dbs.py | 41 +++++++++++++ opt/settings.py | 145 ++++++++++++++++++++++++++++++++++++++++++++++ test/__init__.py | 0 test/__main__.py | 62 ++++++++++++++++++++ 7 files changed, 298 insertions(+) create mode 100644 .gitignore create mode 100644 Readme.md create mode 100644 opt/dbs.py create mode 100755 opt/inject_dbs.py create mode 100644 opt/settings.py create mode 100644 test/__init__.py create mode 100644 test/__main__.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..aa14a2c --- /dev/null +++ b/Readme.md @@ -0,0 +1,18 @@ +virtualenv +---------- + +pip +--- + +Django settings.py +------------------ + +Sets up a db for every ENV ${NAME}_URL => postgres:// var: + DATABASES["DATABASE"] + DATABASES["SHARED_DATABASE"] + DATABASES["HEROKU_POSTGRESQL_ONYX"] + +Aliases DATABASE_URL to default database: + DATABASES["default"] = DATABASES["DATABASE"] + +Injected right after DATABASES = {...} \ No newline at end of file diff --git a/opt/dbs.py b/opt/dbs.py new file mode 100644 index 0000000..b7320b5 --- /dev/null +++ b/opt/dbs.py @@ -0,0 +1,31 @@ + +########## +# BEGIN HEROKU PYTHON/DJANGO LANGUAGE PACK +# Dynamically set DATABASES from ENV vars + +import os, urlparse +urlparse.uses_netloc.append("postgres") +key_re = re.compile(r"^(?P[A-Z_]+)_URL$") + +for k,v in os.environ.items(): + url = urlparse.urlparse(v) + matches = key_re.match(k) + if not matches or url.scheme != "postgres": + continue + + DATABASES[matches.group("name")] = { + "ENGINE": "django.db.backends.postgresql_psycopg2", + "NAME": url.path[1:], + "USER": url.username, + "PASSWORD": url.password, + "HOST": url.hostname, + "PORT": url.port, + } + +# alias "default" to DATABASE_URL +if DATABASES["DATABASE"]: + DATABASES["default"] = DATABASES["DATABASE"] + +# +# END HEROKU PYTHON/DJANGO LANGUAGE PACK +########## diff --git a/opt/inject_dbs.py b/opt/inject_dbs.py new file mode 100755 index 0000000..b005a57 --- /dev/null +++ b/opt/inject_dbs.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python2.7 +import os +import sys +from lib2to3 import pygram, pytree +from lib2to3.pgen2 import driver + +ROOTDIR = os.path.dirname(__file__) + +def find(node, leaf): + if node == leaf: + return node + for c in node.children: + if find(c, leaf): + return find(c, leaf) + +if __name__ == "__main__": + if len(sys.argv) != 2: + print "Usage: inject_dbs.py FILENAME" + sys.exit(1) + + src = sys.argv[1] + patch = os.path.join(ROOTDIR, "dbs.py") + dest = "_settings.py" + + drv = driver.Driver(pygram.python_grammar, pytree.convert) + root = drv.parse_file(src) + + node = find(root, pytree.Leaf(1, "DATABASES")) # Find first DATABASES + end = node.parent.next_sibling + + with open(src) as _src: + head = [_src.next() for x in xrange(end.lineno)] + tail = [l for x,l in enumerate(_src)] + + with open(patch) as _patch: + body = [l for x,l in enumerate(_patch)] + + with open(dest, "w") as _dest: + _dest.writelines(head) + _dest.writelines(body) + _dest.writelines(tail) diff --git a/opt/settings.py b/opt/settings.py new file mode 100644 index 0000000..1707371 --- /dev/null +++ b/opt/settings.py @@ -0,0 +1,145 @@ +# Django settings for myapp project. + +DEBUG = True +TEMPLATE_DEBUG = DEBUG + +ADMINS = ( + # ('Your Name', 'your_email@example.com'), +) + +MANAGERS = ADMINS + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. + 'NAME': '', # Or path to database file if using sqlite3. + 'USER': '', # Not used with sqlite3. + 'PASSWORD': '', # Not used with sqlite3. + 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. + 'PORT': '', # Set to empty string for default. Not used with sqlite3. + } +} + +# Local time zone for this installation. Choices can be found here: +# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name +# although not all choices may be available on all operating systems. +# On Unix systems, a value of None will cause Django to use the same +# timezone as the operating system. +# If running in a Windows environment this must be set to the same as your +# system time zone. +TIME_ZONE = 'America/Chicago' + +# Language code for this installation. All choices can be found here: +# http://www.i18nguy.com/unicode/language-identifiers.html +LANGUAGE_CODE = 'en-us' + +SITE_ID = 1 + +# If you set this to False, Django will make some optimizations so as not +# to load the internationalization machinery. +USE_I18N = True + +# If you set this to False, Django will not format dates, numbers and +# calendars according to the current locale +USE_L10N = True + +# Absolute filesystem path to the directory that will hold user-uploaded files. +# Example: "/home/media/media.lawrence.com/media/" +MEDIA_ROOT = '' + +# URL that handles the media served from MEDIA_ROOT. Make sure to use a +# trailing slash. +# Examples: "http://media.lawrence.com/media/", "http://example.com/media/" +MEDIA_URL = '' + +# Absolute path to the directory static files should be collected to. +# Don't put anything in this directory yourself; store your static files +# in apps' "static/" subdirectories and in STATICFILES_DIRS. +# Example: "/home/media/media.lawrence.com/static/" +STATIC_ROOT = '' + +# URL prefix for static files. +# Example: "http://media.lawrence.com/static/" +STATIC_URL = '/static/' + +# URL prefix for admin static files -- CSS, JavaScript and images. +# Make sure to use a trailing slash. +# Examples: "http://foo.com/static/admin/", "/static/admin/". +ADMIN_MEDIA_PREFIX = '/static/admin/' + +# Additional locations of static files +STATICFILES_DIRS = ( + # Put strings here, like "/home/html/static" or "C:/www/django/static". + # Always use forward slashes, even on Windows. + # Don't forget to use absolute paths, not relative paths. +) + +# List of finder classes that know how to find static files in +# various locations. +STATICFILES_FINDERS = ( + 'django.contrib.staticfiles.finders.FileSystemFinder', + 'django.contrib.staticfiles.finders.AppDirectoriesFinder', +# 'django.contrib.staticfiles.finders.DefaultStorageFinder', +) + +# Make this unique, and don't share it with anybody. +SECRET_KEY = 'urj6yp&_y^_%9*yf0=qtp+6=8%8eschux+m*a4)!s4xp5!$ldy' + +# List of callables that know how to import templates from various sources. +TEMPLATE_LOADERS = ( + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.app_directories.Loader', +# 'django.template.loaders.eggs.Loader', +) + +MIDDLEWARE_CLASSES = ( + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', +) + +ROOT_URLCONF = 'myapp.urls' + +TEMPLATE_DIRS = ( + # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". + # Always use forward slashes, even on Windows. + # Don't forget to use absolute paths, not relative paths. +) + +INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'django.contrib.messages', + 'django.contrib.staticfiles', + # Uncomment the next line to enable the admin: + # 'django.contrib.admin', + # Uncomment the next line to enable admin documentation: + # 'django.contrib.admindocs', +) + +# A sample logging configuration. The only tangible logging +# performed by this configuration is to send an email to +# the site admins on every HTTP 500 error. +# See http://docs.djangoproject.com/en/dev/topics/logging for +# more details on how to customize your logging configuration. +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'handlers': { + 'mail_admins': { + 'level': 'ERROR', + 'class': 'django.utils.log.AdminEmailHandler' + } + }, + 'loggers': { + 'django.request': { + 'handlers': ['mail_admins'], + 'level': 'ERROR', + 'propagate': True, + }, + } +} diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/__main__.py b/test/__main__.py new file mode 100644 index 0000000..ffd2baa --- /dev/null +++ b/test/__main__.py @@ -0,0 +1,62 @@ +import os +import re +import unittest + +DATABASES = {} +ROOTDIR = os.path.dirname(__file__) + +class TestInjectDBs(unittest.TestCase): + def setUp(self): + DATABASES = {} + os.environ.update({ + "DATABASE_URL": "postgres://victor:xray@localhost:5432/alfa", + "HEROKU_POSTGRESQL_ONYX_URL": "postgres://victor:xray@localhost:5432/alfa", + "HEROKU_POSTGRESQL_RED_URL": "postgres://juliet:zulu@localhost:5432/beta", + "SHARED_DATABASE_URL": "postgres://quebec:kilo@localhost:5432/echo", + "THUNK": "postgres://localhost/db", + "FOO": "bar", + }) + + def testCode(self): + """ + Test the code injected into settings.py to map ENV to settings.DATABASES hash + """ + # read and exec code in this context + with open(os.path.join(ROOTDIR, "..", "opt/dbs.py")) as _src: + exec compile(_src.read(), "dbs.py", "exec") + + self.assertEqual(5, len(DATABASES)) # default, DATABASE, ONYX, RED, SHARED_DATABASE + + self.assertDictEqual({ + "ENGINE": "django.db.backends.postgresql_psycopg2", + "NAME": "alfa", + "USER": "victor", + "PASSWORD": "xray", + "HOST": "localhost", + "PORT": 5432 + }, DATABASES["HEROKU_POSTGRESQL_ONYX"]) + + self.assertDictEqual({ + "ENGINE": "django.db.backends.postgresql_psycopg2", + "NAME": "beta", + "USER": "juliet", + "PASSWORD": "zulu", + "HOST": "localhost", + "PORT": 5432 + }, DATABASES["HEROKU_POSTGRESQL_RED"]) + + self.assertDictEqual({ + "ENGINE": "django.db.backends.postgresql_psycopg2", + "NAME": "echo", + "USER": "quebec", + "PASSWORD": "kilo", + "HOST": "localhost", + "PORT": 5432 + }, DATABASES["SHARED_DATABASE"]) + + # aliases + self.assertDictEqual(DATABASES["HEROKU_POSTGRESQL_ONYX"], DATABASES["DATABASE"]) + self.assertDictEqual(DATABASES["HEROKU_POSTGRESQL_ONYX"], DATABASES["default"]) + +if __name__ == "__main__": + unittest.main() \ No newline at end of file