Commits

Kirill Simonov  committed 6da78f9

Added `tweak.django` addon for integration with Django.

  • Participants
  • Parent commits 9a0007f

Comments (0)

Files changed (10)

         'tweak = htsql_tweak:TweakAddon',
         'tweak.autolimit = htsql_tweak.autolimit:TweakAutolimitAddon',
         'tweak.cors = htsql_tweak.cors:TweakCORSAddon',
+        'tweak.hello = htsql_tweak.hello:TweakHelloAddon',
+        'tweak.django = htsql_tweak.django:TweakDjangoAddon',
         'tweak.inet = htsql_tweak.inet:TweakINetAddon',
         'tweak.inet.pgsql = htsql_tweak.inet.pgsql:TweakINetPGSQLAddon',
-        'tweak.hello = htsql_tweak.hello:TweakHelloAddon',
+        'tweak.meta = htsql_tweak.meta:TweakMetaAddon',
+        'tweak.meta.slave = htsql_tweak.meta.slave:TweakMetaSlaveAddon',
+        'tweak.override = htsql_tweak.override:TweakOverrideAddon',
+        'tweak.resource = htsql_tweak.resource:TweakResourceAddon',
+        'tweak.shell = htsql_tweak.shell:TweakShellAddon',
+        'tweak.shell.default = htsql_tweak.shell.default:TweakShellDefaultAddon',
+        'tweak.sqlalchemy = htsql_tweak.sqlalchemy:TweakSQLAlchemyAddon',
         'tweak.system = htsql_tweak.system:TweakSystemAddon',
         'tweak.system.pgsql = htsql_tweak.system.pgsql:TweakSystemPGSQLAddon',
         'tweak.timeout = htsql_tweak.timeout:TweakTimeoutAddon',
             ' = htsql_tweak.timeout.pgsql:TweakTimeoutPGSQLAddon',
         'tweak.view = htsql_tweak.view:TweakViewAddon',
         'tweak.view.pgsql = htsql_tweak.view.pgsql:TweakViewPGSQLAddon',
-        'tweak.sqlalchemy = htsql_tweak.sqlalchemy:TweakSQLAlchemyAddon',
-        'tweak.meta = htsql_tweak.meta:TweakMetaAddon',
-        'tweak.meta.slave = htsql_tweak.meta.slave:TweakMetaSlaveAddon',
-        'tweak.resource = htsql_tweak.resource:TweakResourceAddon',
-        'tweak.shell = htsql_tweak.shell:TweakShellAddon',
-        'tweak.shell.default = htsql_tweak.shell.default:TweakShellDefaultAddon',
-        'tweak.override = htsql_tweak.override:TweakOverrideAddon',
     ],
 }
 INSTALL_REQUIRES = [

File src/htsql_engine/sqlite/connect.py

     def convert(value):
         if value is None:
             return None
+        if isinstance(value, bool):
+            return value
         if not isinstance(value, int):
             raise SQLiteError("expected a Boolean value, got %r" % value)
         return (value != 0)
     def convert(value):
         if value is None:
             return None
+        if isinstance(value, datetime.date):
+            return value
         if not isinstance(value, (str, unicode)):
             raise SQLiteError("expected a date value, got %r" % value)
         converter = sqlite3.converters['DATE']
     def convert(value):
         if value is None:
             return None
+        if isinstance(value, datetime.time):
+            return value
         if not isinstance(value, (str, unicode)):
             raise SQLiteError("expected a time value, got %r" % value)
         # FIXME: verify that the value is in valid format.
     def convert(value):
         if value is None:
             return None
+        if isinstance(value, datetime.datetime):
+            return value
         if not isinstance(value, (str, unicode)):
             raise SQLiteError("expected a timestamp value, got %r" % value)
         converter = sqlite3.converters['TIMESTAMP']

File src/htsql_tweak/django/__init__.py

+#
+# Copyright (c) 2011, Prometheus Research, LLC
+# See `LICENSE` for license information, `AUTHORS` for the list of authors.
+#
+
+
+from htsql.validator import StrVal
+from htsql.addon import Addon, Parameter
+from htsql.util import DB
+import os
+
+
+class TweakDjangoAddon(Addon):
+
+    prerequisites = []
+    postrequisites = ['htsql']
+    name = 'tweak.django'
+    hint = """provide Django integration"""
+    help = """
+    This addon replaces built-in database introspection and
+    connection handling with Django facilities.
+
+    Parameter `settings` is the path to the settings module.  If not
+    set, environment variable `DJANGO_SETTINGS_MODULE` is used.
+    """
+
+    parameters = [
+            Parameter('settings', StrVal(),
+                      value_name="PROJECT.MODULE",
+                      hint="""name of the `settings` module"""),
+    ]
+
+    @classmethod
+    def get_extension(cls, app, attributes):
+        settings = attributes['settings']
+        if settings is not None:
+            os.environ['DJANGO_SETTINGS_MODULE'] = settings
+        from . import connect, introspect
+        from django.conf import settings
+        from django.db.utils import DEFAULT_DB_ALIAS
+        engine = settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE']
+        engine = {
+                'django.db.backends.postgresql_psycopg2': 'pgsql',
+                'django.db.backends.postgresql': 'pgsql',
+                'django.db.backends.mysql': 'mysql',
+                'django.db.backends.sqlite3': 'sqlite',
+                'django.db.backends.oracle': 'oracle',
+        }.get(engine, engine)
+        username = settings.DATABASES[DEFAULT_DB_ALIAS]['USER']
+        if not username:
+            username = None
+        password = settings.DATABASES[DEFAULT_DB_ALIAS]['PASSWORD']
+        if not password:
+            password = None
+        host = settings.DATABASES[DEFAULT_DB_ALIAS]['HOST']
+        if not host:
+            host = None
+        port = settings.DATABASES[DEFAULT_DB_ALIAS]['PORT']
+        if not port:
+            port = None
+        else:
+            port = int(port)
+        database = settings.DATABASES[DEFAULT_DB_ALIAS]['NAME']
+        return {
+            'htsql': {
+                'db': DB(engine=engine,
+                         username=username,
+                         password=password,
+                         host=host,
+                         port=port,
+                         database=database),
+                },
+            'engine.%s' % engine : {},
+        }
+
+

File src/htsql_tweak/django/connect.py

+#
+# Copyright (c) 2011, Prometheus Research, LLC
+# See `LICENSE` for license information, `AUTHORS` for the list of authors.
+#
+
+from htsql.context import context
+from htsql.connect import Connect
+from htsql.adapter import weigh
+
+
+class DjangoConnect(Connect):
+
+    weigh(2.0) # ensure connections here are not pooled
+
+    def open(self):
+        from django.db import connections
+        from django.db.utils import DEFAULT_DB_ALIAS
+        return connections[DEFAULT_DB_ALIAS]
+
+

File src/htsql_tweak/django/introspect.py

+#
+# Copyright (c) 2011, Prometheus Research, LLC
+# See `LICENSE` for license information, `AUTHORS` for the list of authors.
+#
+
+
+from htsql.context import context
+from htsql.adapter import Protocol, weigh, named
+from htsql.introspect import Introspect
+from htsql.entity import make_catalog
+from htsql.domain import (BooleanDomain, IntegerDomain, FloatDomain,
+                          DecimalDomain, StringDomain, DateDomain, TimeDomain,
+                          DateTimeDomain, OpaqueDomain)
+
+
+class DjangoIntrospect(Introspect):
+
+    weigh(1.0)
+
+    def __call__(self):
+        from django.db import connections, models
+        from django.db.utils import DEFAULT_DB_ALIAS
+        from django.conf import settings
+        is_upper = (settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] in
+                                ['django.db.backends.oracle'])
+        connection = connections[DEFAULT_DB_ALIAS]
+        catalog = make_catalog()
+        tables = connection.introspection.table_names()
+        seen_models = connection.introspection.installed_models(tables)
+        all_models = []
+        for app in models.get_apps():
+            for model in models.get_models(app, include_auto_created=True):
+                if model not in seen_models:
+                    continue
+                all_models.append(model)
+        schema = catalog.add_schema(u"")
+        relations = []
+        table_by_model = {}
+        column_by_field = {}
+        for model in all_models:
+            meta = model._meta
+            name = meta.db_table
+            if is_upper:
+                name = name.upper()
+            if isinstance(name, str):
+                name = name.decode('utf-8')
+            table = schema.add_table(name)
+            table_by_model[model] = table
+            for field in meta.local_fields:
+                introspect_domain = IntrospectDjangoDomain(field)
+                domain = introspect_domain()
+                if domain is None:
+                    continue
+                name = field.column
+                if is_upper:
+                    name = name.upper()
+                if isinstance(name, str):
+                    name = name.decode('utf-8')
+                is_nullable = bool(field.null)
+                column = table.add_column(name, domain, is_nullable)
+                column_by_field[field] = column
+                if field.primary_key:
+                    table.add_primary_key([column])
+                if field.unique:
+                    table.add_unique_key([column])
+                if field.rel is not None:
+                    relations.append(field)
+        for field in relations:
+            table = table_by_model[field.model]
+            column = column_by_field[field]
+            target_field = field.rel.get_related_field()
+            if target_field not in column_by_field:
+                continue
+            target_table = table_by_model[target_field.model]
+            target_column = column_by_field[target_field]
+            table.add_foreign_key([column], target_table, [target_column])
+        return catalog
+
+
+class IntrospectDjangoDomain(Protocol):
+
+    @classmethod
+    def dispatch(cls, field):
+        return field.__class__.__name__
+
+    def __init__(self, field):
+        self.field = field
+
+    def __call__(self):
+        return OpaqueDomain()
+
+
+class IntrospectDjangoBooleanDomain(IntrospectDjangoDomain):
+
+    named('BooleanField', 'NullBooleanField')
+
+    def __call__(self):
+        return BooleanDomain()
+
+
+class IntrospectDjangoIntegerDomain(IntrospectDjangoDomain):
+
+    named('AutoField', 'IntegerField', 'ForeignKey')
+
+    def __call__(self):
+        return IntegerDomain()
+
+
+class IntrospectDjangoFloatDomain(IntrospectDjangoDomain):
+
+    named('FloatField')
+
+    def __call__(self):
+        return FloatDomain()
+
+
+class IntrospectDjangoDecimalDomain(IntrospectDjangoDomain):
+
+    named('DecimalField')
+
+    def __call__(self):
+        return DecimalDomain()
+
+
+class IntrospectDjangoStringDomain(IntrospectDjangoDomain):
+
+    named('CharField', 'FilePathField', 'TextField')
+
+    def __call__(self):
+        return StringDomain()
+
+
+class IntrospectDjangoDateDomain(IntrospectDjangoDomain):
+
+    named('DateField')
+
+    def __call__(self):
+        return DateDomain()
+
+
+class IntrospectDjangoTimeDomain(IntrospectDjangoDomain):
+
+    named('TimeField')
+
+    def __call__(self):
+        return TimeDomain()
+
+
+class IntrospectDjangoDateTimeDomain(IntrospectDjangoDomain):
+
+    named('DateTimeField')
+
+    def __call__(self):
+        return DateTimeDomain()
+
+
+class IntrospectDjangoNotDomain(IntrospectDjangoDomain):
+
+    named('ManyToManyField')
+
+    def __call__(self):
+        return None
+
+

File test/input/addon.yaml

   ifdef: django
   tests:
   # Addon description
-#  - ctl: [ext, tweak.django]
+  - ctl: [ext, tweak.django]
 
   # Load `test_django_sandbox` and deploy the database
   - py: add-module-path
       createdb()
 
   # Test Django-generated database
-  - load: sandbox
+  - db: null
+    extensions:
+      tweak.django:
+        settings: test_django_sandbox.settings
   - uri: /polls_poll{question, pub_date, count(polls_choice)}
   - uri: /polls_choice{poll.question, choice, votes}
 

File test/output/mysql.yaml

         stdout: ''
       - id: tweak.django
         tests:
+        - ctl: [ext, tweak.django]
+          stdout: |+
+            TWEAK.DJANGO - provide Django integration
+
+            This addon replaces built-in database introspection and
+            connection handling with Django facilities.
+
+            Parameter `settings` is the path to the settings module.  If not
+            set, environment variable `DJANGO_SETTINGS_MODULE` is used.
+
+            Parameters:
+              settings=PROJECT.MODULE  : name of the `settings` module
+
+          exit: 0
         - py: add-module-path
           stdout: ''
         - uri: /polls_poll{question, pub_date, count(polls_choice)}

File test/output/oracle.yaml

         stdout: ''
       - id: tweak.django
         tests:
+        - ctl: [ext, tweak.django]
+          stdout: |+
+            TWEAK.DJANGO - provide Django integration
+
+            This addon replaces built-in database introspection and
+            connection handling with Django facilities.
+
+            Parameter `settings` is the path to the settings module.  If not
+            set, environment variable `DJANGO_SETTINGS_MODULE` is used.
+
+            Parameters:
+              settings=PROJECT.MODULE  : name of the `settings` module
+
+          exit: 0
         - py: add-module-path
           stdout: ''
         - uri: /polls_poll{question, pub_date, count(polls_choice)}
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | polls_poll                                             |
-             +--------------------------------------------------------+
-             | question   | pub_date            | count(polls_choice) |
-            -+------------+---------------------+---------------------+-
-             | What's up? | 2011-01-01 00:00:00 |                   3 |
-                                                                (1 row)
+             | polls_poll                                    |
+             +-----------------------------------------------+
+             | question   | pub_date   | count(polls_choice) |
+            -+------------+------------+---------------------+-
+             | What's up? | 2011-01-01 |                   3 |
+                                                       (1 row)
 
              ----
              /polls_poll{question,pub_date,count(polls_choice)}

File test/output/pgsql.yaml

         stdout: ''
       - id: tweak.django
         tests:
+        - ctl: [ext, tweak.django]
+          stdout: |+
+            TWEAK.DJANGO - provide Django integration
+
+            This addon replaces built-in database introspection and
+            connection handling with Django facilities.
+
+            Parameter `settings` is the path to the settings module.  If not
+            set, environment variable `DJANGO_SETTINGS_MODULE` is used.
+
+            Parameters:
+              settings=PROJECT.MODULE  : name of the `settings` module
+
+          exit: 0
         - py: add-module-path
           stdout: ''
         - uri: /polls_poll{question, pub_date, count(polls_choice)}
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |2
-             | polls_poll                                                   |
-             +--------------------------------------------------------------+
-             | question   | pub_date                  | count(polls_choice) |
-            -+------------+---------------------------+---------------------+-
-             | What's up? | 2011-01-01 01:00:00-05:00 |                   3 |
-                                                                      (1 row)
+             | polls_poll                                    |
+             +-----------------------------------------------+
+             | question   | pub_date   | count(polls_choice) |
+            -+------------+------------+---------------------+-
+             | What's up? | 2011-01-01 |                   3 |
+                                                       (1 row)
 
              ----
              /polls_poll{question,pub_date,count(polls_choice)}

File test/output/sqlite.yaml

         stdout: ''
       - id: tweak.django
         tests:
+        - ctl: [ext, tweak.django]
+          stdout: |+
+            TWEAK.DJANGO - provide Django integration
+
+            This addon replaces built-in database introspection and
+            connection handling with Django facilities.
+
+            Parameter `settings` is the path to the settings module.  If not
+            set, environment variable `DJANGO_SETTINGS_MODULE` is used.
+
+            Parameters:
+              settings=PROJECT.MODULE  : name of the `settings` module
+
+          exit: 0
         - py: add-module-path
           stdout: ''
         - uri: /polls_poll{question, pub_date, count(polls_choice)}