Commits

Mike Bayer  committed 92c3fa4

initial rev

  • Participants

Comments (0)

Files changed (14)

+syntax:regexp
+^build/
+^dist/
+^docs/build/output
+.pyc$
+.orig$
+.egg-info
+.coverage
+.venv
+.windows_venv
+local_test
+SQLAlchemy was created by Michael Bayer.
+
+Major contributing authors include:
+
+- Michael Bayer <mike_mp@zzzcomputing.com>
+- Jason Kirtland <jek@discorporate.us>
+- Gaetan de Menten <gdementen@gmail.com>
+- Diana Clarke <diana.joan.clarke@gmail.com>
+- Michael Trier <mtrier@gmail.com>
+- Philip Jenvey <pjenvey@underboss.org>
+- Ants Aasma <ants.aasma@gmail.com>
+- Paul Johnston <paj@pajhome.org.uk>
+- Jonathan Ellis <jbellis@gmail.com>
+
+For a larger list of SQLAlchemy contributors over time, see:
+
+http://www.sqlalchemy.org/trac/wiki/Contributors
+
+This is the MIT license: http://www.opensource.org/licenses/mit-license.php
+
+Copyright (c) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>. 
+SQLAlchemy is a trademark of Michael Bayer.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this
+software and associated documentation files (the "Software"), to deal in the Software
+without restriction, including without limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or
+substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+# any kind of "*" pulls in __init__.pyc files,
+# so all extensions are explicit.
+
+recursive-include doc *.html *.css *.txt *.js *.jpg *.png *.py Makefile *.rst *.mako *.sty
+recursive-include examples *.py *.xml
+recursive-include test *.py *.dat
+
+# include the c extensions, which otherwise
+# don't come in if --with-cextensions isn't specified.
+recursive-include lib *.c *.txt
+
+include README* LICENSE distribute_setup.py sa2to3.py ez_setup.py sqla_nose.py CHANGES*
+prune doc/build/output
+A Microsoft Access dialect for SQLAlchemy.

File run_tests.py

+from sqlalchemy.dialects import registry
+
+registry.register("access", "sqlalchemy_access.pyodbc", "AccessDialect_pyodbc")
+registry.register("access.pyodbc", "sqlalchemy_access.pyodbc", "AccessDialect_pyodbc")
+
+from sqlalchemy.testing import runner
+
+runner.main()
+[egg_info]
+tag_build = dev
+
+[nosetests]
+with-sqla_testing = true
+where = test
+cover-package = sqlalchemy_akiban
+with-coverage = 1
+cover-erase = 1
+
+[sqla_testing]
+requirement_cls=test.requirements:Requirements
+profile_file=.profiles.txt
+
+[db]
+default=access+pyodbc://admin@access_test
+sqlite=sqlite:///:memory:
+
+import os
+import re
+
+from setuptools import setup
+
+v = open(os.path.join(os.path.dirname(__file__), 'sqlalchemy_access', '__init__.py'))
+VERSION = re.compile(r".*__version__ = '(.*?)'", re.S).match(v.read()).group(1)
+v.close()
+
+readme = os.path.join(os.path.dirname(__file__), 'README.rst')
+
+
+setup(name='sqlalchemy_access',
+      version=VERSION,
+      description="MS Access for SQLAlchemy",
+      long_description=open(readme).read(),
+      classifiers=[
+      'Development Status :: 4 - Beta',
+      'Environment :: Console',
+      'Intended Audience :: Developers',
+      'Programming Language :: Python',
+      'Programming Language :: Python :: 3',
+      'Programming Language :: Python :: Implementation :: CPython',
+      'Programming Language :: Python :: Implementation :: PyPy',
+      'Topic :: Database :: Front-Ends',
+      ],
+      keywords='SQLAlchemy Microsoft Access',
+      author='Mike Bayer',
+      author_email='mike@zzzcomputing.com',
+      license='MIT',
+      packages=['sqlalchemy_access'],
+      include_package_data=True,
+      tests_require=['nose >= 0.11'],
+      test_suite="nose.collector",
+      zip_safe=False,
+      entry_points={
+         'sqlalchemy.dialects': [
+              'access = sqlalchemy_access.pyodbc:AccessDialect_pyodbc',
+              'access.pyodbc = sqlalchemy_access.pyodbc:AccessDialect_pyodbc',
+              ]
+        }
+)

File sqlalchemy_access/__init__.py

+__version__ = '0.8'
+
+from sqlalchemy.dialects import registry
+
+registry.register("access", "sqlalchemy_access.pyodbc", "AccessDialect_pyodbc")
+registry.register("access.pyodbc", "sqlalchemy_access.pyodbc", "AccessDialect_pyodbc")

File sqlalchemy_access/base.py

+# access/base.py
+# Copyright (C) 2007-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
+# Copyright (C) 2007 Paul Johnston, paj@pajhome.org.uk
+# Portions derived from jet2sql.py by Matt Keranen, mksql@yahoo.com
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
+
+"""
+Support for the Microsoft Access database.
+
+
+"""
+from sqlalchemy import sql, schema, types, exc, pool
+from sqlalchemy.sql import compiler, expression
+from sqlalchemy.engine import default, base, reflection
+from sqlalchemy import processors
+
+class AcNumeric(types.Numeric):
+    def get_col_spec(self):
+        return "NUMERIC"
+
+    def bind_processor(self, dialect):
+        return processors.to_str
+
+    def result_processor(self, dialect, coltype):
+        return None
+
+class AcFloat(types.Float):
+    def get_col_spec(self):
+        return "FLOAT"
+
+    def bind_processor(self, dialect):
+        """By converting to string, we can use Decimal types round-trip."""
+        return processors.to_str
+
+class AcInteger(types.Integer):
+    def get_col_spec(self):
+        return "INTEGER"
+
+class AcTinyInteger(types.Integer):
+    def get_col_spec(self):
+        return "TINYINT"
+
+class AcSmallInteger(types.SmallInteger):
+    def get_col_spec(self):
+        return "SMALLINT"
+
+class AcDateTime(types.DateTime):
+    def get_col_spec(self):
+        return "DATETIME"
+
+class AcDate(types.Date):
+
+    def get_col_spec(self):
+        return "DATETIME"
+
+class AcText(types.Text):
+    def get_col_spec(self):
+        return "MEMO"
+
+class AcString(types.String):
+    def get_col_spec(self):
+        return "TEXT" + (self.length and ("(%d)" % self.length) or "")
+
+class AcUnicode(types.Unicode):
+    def get_col_spec(self):
+        return "TEXT" + (self.length and ("(%d)" % self.length) or "")
+
+    def bind_processor(self, dialect):
+        return None
+
+    def result_processor(self, dialect, coltype):
+        return None
+
+class AcChar(types.CHAR):
+    def get_col_spec(self):
+        return "TEXT" + (self.length and ("(%d)" % self.length) or "")
+
+class AcBinary(types.LargeBinary):
+    def get_col_spec(self):
+        return "BINARY"
+
+class AcBoolean(types.Boolean):
+    def get_col_spec(self):
+        return "YESNO"
+
+class AcTimeStamp(types.TIMESTAMP):
+    def get_col_spec(self):
+        return "TIMESTAMP"
+
+class AccessExecutionContext(default.DefaultExecutionContext):
+
+    def get_lastrowid(self):
+        self.cursor.execute("SELECT @@identity AS lastrowid")
+        return self.cursor.fetchone()[0]
+
+
+class AccessCompiler(compiler.SQLCompiler):
+    extract_map = compiler.SQLCompiler.extract_map.copy()
+    extract_map.update({
+            'month': 'm',
+            'day': 'd',
+            'year': 'yyyy',
+            'second': 's',
+            'hour': 'h',
+            'doy': 'y',
+            'minute': 'n',
+            'quarter': 'q',
+            'dow': 'w',
+            'week': 'ww'
+    })
+
+    def visit_cast(self, cast, **kwargs):
+        return cast.clause._compiler_dispatch(self, **kwargs)
+
+    def visit_select_precolumns(self, select):
+        """Access puts TOP, it's version of LIMIT here """
+        s = select.distinct and "DISTINCT " or ""
+        if select.limit:
+            s += "TOP %s " % (select.limit)
+        if select.offset:
+            raise exc.InvalidRequestError(
+                    'Access does not support LIMIT with an offset')
+        return s
+
+    def limit_clause(self, select):
+        """Limit in access is after the select keyword"""
+        return ""
+
+    def binary_operator_string(self, binary):
+        """Access uses "mod" instead of "%" """
+        return binary.operator == '%' and 'mod' or binary.operator
+
+    function_rewrites = {'current_date': 'now',
+                          'current_timestamp': 'now',
+                          'length': 'len',
+                          }
+    def visit_function(self, func):
+        """Access function names differ from the ANSI SQL names;
+        rewrite common ones"""
+        func.name = self.function_rewrites.get(func.name, func.name)
+        return super(AccessCompiler, self).visit_function(func)
+
+    def for_update_clause(self, select):
+        """FOR UPDATE is not supported by Access; silently ignore"""
+        return ''
+
+    # Strip schema
+    def visit_table(self, table, asfrom=False, **kwargs):
+        if asfrom:
+            return self.preparer.quote(table.name, table.quote)
+        else:
+            return ""
+
+    def visit_join(self, join, asfrom=False, **kwargs):
+        return (self.process(join.left, asfrom=True) + \
+                (join.isouter and " LEFT OUTER JOIN " or " INNER JOIN ") + \
+                self.process(join.right, asfrom=True) + " ON " + \
+                self.process(join.onclause))
+
+    def visit_extract(self, extract, **kw):
+        field = self.extract_map.get(extract.field, extract.field)
+        return 'DATEPART("%s", %s)' % \
+                    (field, self.process(extract.expr, **kw))
+
+class AccessDDLCompiler(compiler.DDLCompiler):
+    def get_column_specification(self, column, **kwargs):
+        if column.table is None:
+            raise exc.CompileError(
+                            "access requires Table-bound columns "
+                            "in order to generate DDL")
+
+        colspec = self.preparer.format_column(column)
+        seq_col = column.table._autoincrement_column
+        if seq_col is column:
+            colspec += " AUTOINCREMENT"
+        else:
+            colspec += " " + self.dialect.type_compiler.process(column.type)
+
+            if column.nullable is not None and not column.primary_key:
+                if not column.nullable or column.primary_key:
+                    colspec += " NOT NULL"
+                else:
+                    colspec += " NULL"
+
+            default = self.get_column_default_string(column)
+            if default is not None:
+                colspec += " DEFAULT " + default
+
+        return colspec
+
+    def visit_drop_index(self, drop):
+        index = drop.element
+        self.append("\nDROP INDEX [%s].[%s]" % \
+                        (index.table.name,
+                        self._index_identifier(index.name)))
+
+class AccessIdentifierPreparer(compiler.IdentifierPreparer):
+    reserved_words = compiler.RESERVED_WORDS.copy()
+    reserved_words.update(['value', 'text'])
+    def __init__(self, dialect):
+        super(AccessIdentifierPreparer, self).\
+                __init__(dialect, initial_quote='[', final_quote=']')
+
+
+
+class AccessDialect(default.DefaultDialect):
+    colspecs = {
+        types.Unicode: AcUnicode,
+        types.Integer: AcInteger,
+        types.SmallInteger: AcSmallInteger,
+        types.Numeric: AcNumeric,
+        types.Float: AcFloat,
+        types.DateTime: AcDateTime,
+        types.Date: AcDate,
+        types.String: AcString,
+        types.LargeBinary: AcBinary,
+        types.Boolean: AcBoolean,
+        types.Text: AcText,
+        types.CHAR: AcChar,
+        types.TIMESTAMP: AcTimeStamp,
+    }
+    name = 'access'
+    supports_sane_rowcount = False
+    supports_sane_multi_rowcount = False
+
+    poolclass = pool.SingletonThreadPool
+    statement_compiler = AccessCompiler
+    ddl_compiler = AccessDDLCompiler
+    preparer = AccessIdentifierPreparer
+    execution_ctx_cls = AccessExecutionContext
+
+    @classmethod
+    def dbapi(cls):
+        import pyodbc as module
+        return module
+
+    def create_connect_args(self, url):
+        opts = url.translate_connect_args()
+        connectors = ["Driver={Microsoft Access Driver (*.mdb)}"]
+        connectors.append("Dbq=%s" % opts["database"])
+        user = opts.get("username", None)
+        if user:
+            connectors.append("UID=%s" % user)
+            connectors.append("PWD=%s" % opts.get("password", ""))
+        return [[";".join(connectors)], {}]
+
+    def last_inserted_ids(self):
+        return self.context.last_inserted_ids
+
+
+    def has_table(self, connection, tablename, schema=None):
+        result = connection.scalar(
+                        sql.text(
+                            "select count(*) from msysobjects where "
+                            "type=1 and name=:name"), name=tablename
+                        )
+        return bool(result)
+
+    @reflection.cache
+    def get_table_names(self, connection, schema=None, **kw):
+        result = connection.execute("select name from msysobjects where "
+                "type=1 and name not like 'MSys%'")
+        table_names = [r[0] for r in result]
+        return table_names
+
+

File sqlalchemy_access/pyodbc.py

+# access/pyodbc.py
+# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
+
+"""
+Support for Microsoft Access via pyodbc.
+
+pyodbc is available at:
+
+    http://pypi.python.org/pypi/pyodbc/
+
+Connecting
+^^^^^^^^^^
+
+Examples of pyodbc connection string URLs:
+
+* ``mssql+pyodbc://mydsn`` - connects using the specified DSN named ``mydsn``.
+
+"""
+
+
+from .base import AccessExecutionContext, AccessDialect
+from sqlalchemy.connectors.pyodbc import PyODBCConnector
+from sqlalchemy import types as sqltypes, util
+from sqlalchemy.util.compat import decimal
+
+class _AccessNumeric_pyodbc(sqltypes.Numeric):
+    """Turns Decimals with adjusted() < 0 or > 7 into strings.
+
+    The routines here are needed for older pyodbc versions
+    as well as current mxODBC versions.
+
+    """
+
+    def bind_processor(self, dialect):
+
+        super_process = super(_AccessNumeric_pyodbc, self).\
+                        bind_processor(dialect)
+
+        if not dialect._need_decimal_fix:
+            return super_process
+
+        def process(value):
+            if self.asdecimal and \
+                    isinstance(value, decimal.Decimal):
+
+                adjusted = value.adjusted()
+                if adjusted < 0:
+                    return self._small_dec_to_string(value)
+                elif adjusted > 7:
+                    return self._large_dec_to_string(value)
+
+            if super_process:
+                return super_process(value)
+            else:
+                return value
+        return process
+
+    # these routines needed for older versions of pyodbc.
+    # as of 2.1.8 this logic is integrated.
+
+    def _small_dec_to_string(self, value):
+        return "%s0.%s%s" % (
+                    (value < 0 and '-' or ''),
+                    '0' * (abs(value.adjusted()) - 1),
+                    "".join([str(nint) for nint in value.as_tuple()[1]]))
+
+    def _large_dec_to_string(self, value):
+        _int = value.as_tuple()[1]
+        if 'E' in str(value):
+            result = "%s%s%s" % (
+                    (value < 0 and '-' or ''),
+                    "".join([str(s) for s in _int]),
+                    "0" * (value.adjusted() - (len(_int)-1)))
+        else:
+            if (len(_int) - 1) > value.adjusted():
+                result = "%s%s.%s" % (
+                (value < 0 and '-' or ''),
+                "".join(
+                    [str(s) for s in _int][0:value.adjusted() + 1]),
+                "".join(
+                    [str(s) for s in _int][value.adjusted() + 1:]))
+            else:
+                result = "%s%s" % (
+                (value < 0 and '-' or ''),
+                "".join(
+                    [str(s) for s in _int][0:value.adjusted() + 1]))
+        return result
+
+
+class AccessExecutionContext_pyodbc(AccessExecutionContext):
+    pass
+
+
+class AccessDialect_pyodbc(PyODBCConnector, AccessDialect):
+
+    execution_ctx_cls = AccessExecutionContext_pyodbc
+
+    pyodbc_driver_name = 'Microsoft Access'
+
+    colspecs = util.update_copy(
+        AccessDialect.colspecs,
+        {
+            sqltypes.Numeric:_AccessNumeric_pyodbc
+        }
+    )
+

File test/__init__.py

Empty file added.

File test/requirements.py

+from sqlalchemy.testing.requirements import SuiteRequirements
+
+class Requirements(SuiteRequirements):
+    pass

File test/test_suite.py

+from sqlalchemy.testing.suite import *
+
+
+from sqlalchemy.testing.suite import ComponentReflectionTest as _ComponentReflectionTest
+
+class ComponentReflectionTest(_ComponentReflectionTest):
+    @classmethod
+    def define_views(cls, metadata, schema):
+        return
+        for table_name in ('users', 'email_addresses'):
+            fullname = table_name
+            if schema:
+                fullname = "%s.%s" % (schema, table_name)
+            view_name = fullname + '_v'
+            query = "CREATE VIEW %s AS SELECT * FROM %s" % (
+                                view_name, fullname)
+            event.listen(
+                metadata,
+                "after_create",
+                DDL(query)
+            )
+            event.listen(
+                metadata,
+                "before_drop",
+                DDL("DROP VIEW %s" % view_name)
+            )