Initial upload
This commit is contained in:
commit
c59faac144
|
@ -0,0 +1,53 @@
|
||||||
|
|
||||||
|
Experimental Django backend using MySQL Connector/Python
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
Disclaimer
|
||||||
|
=====================
|
||||||
|
|
||||||
|
!!!!!!!!!!! THIS IS STILL IN DEVELOPMENT !!!!!!!!!!!!!!!!!!!
|
||||||
|
!!!!!!!! EXPECT THING TO NOT WORK OR GO WRONG !!!!!!!!!!!!!!
|
||||||
|
!!!! DO NOT USE IN PRODUCTION etc.. etc... !!!!!!!!!!!!!!!!!
|
||||||
|
|
||||||
|
Ah, and make backups! :-)
|
||||||
|
|
||||||
|
Dependencies
|
||||||
|
=====================
|
||||||
|
|
||||||
|
* Python 2.3 or greater.
|
||||||
|
* Django 1.2: http://www.djangoproject.com
|
||||||
|
* MySQL Connector/Python (currently in development)
|
||||||
|
shell> bzr checkout lp:~mysql/myconnpy/main myconnpy
|
||||||
|
shell> cd myconnpy
|
||||||
|
shell> python setup.py install
|
||||||
|
|
||||||
|
Installation
|
||||||
|
=====================
|
||||||
|
|
||||||
|
To install the Django backend, do the following:
|
||||||
|
shell> python ./setup.py install
|
||||||
|
|
||||||
|
It will install it in site-packages/mysql/django
|
||||||
|
|
||||||
|
Usage
|
||||||
|
=====================
|
||||||
|
|
||||||
|
To configure your Django project to use the backend, set the engine in
|
||||||
|
your settings.py like this:
|
||||||
|
|
||||||
|
DATABASE_ENGINE='mysql.django'
|
||||||
|
|
||||||
|
The above assumes you installed the mysql.django module somewhere where
|
||||||
|
Python can find it (see Installation).
|
||||||
|
|
||||||
|
Some caveats though:
|
||||||
|
* When you were using the DATABASE_OPTIONS and stored settings in an
|
||||||
|
option file, that will not work anymore. Do it the normal Django way.
|
||||||
|
* You can't use UNIX Sockets (yet).
|
||||||
|
|
||||||
|
Report problems
|
||||||
|
=====================
|
||||||
|
|
||||||
|
Report problems to Geert Vanderkelen <geert.vanderkelen@sun.com>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
"""
|
||||||
|
Connector/Python, native MySQL driver written in Python.
|
||||||
|
Copyright 2009 Sun Microsystems, Inc. All rights reserved. Use is subject to license terms.
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
"""
|
||||||
|
# This file should be automatically generated on each release
|
||||||
|
version = (0, 0, 1, 'alpha', '')
|
|
@ -0,0 +1,238 @@
|
||||||
|
"""
|
||||||
|
MySQL database backend for Django using MySQL Connector/Python.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util
|
||||||
|
try:
|
||||||
|
import mysql.connector as Database
|
||||||
|
except ImportError, e:
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
raise ImproperlyConfigured("Error loading MySQLdb module: %s" % e)
|
||||||
|
|
||||||
|
# We want version (1, 2, 1, 'final', 2) or later. We can't just use
|
||||||
|
# lexicographic ordering in this check because then (1, 2, 1, 'gamma')
|
||||||
|
# inadvertently passes the version test.
|
||||||
|
version = Database.__version__
|
||||||
|
if ( version[:3] < (0, 0, 2) ):
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
raise ImproperlyConfigured("MySQL Connector/Python 0.0.2 or newer is required; you have %s" % Database.__version__)
|
||||||
|
|
||||||
|
import mysql.connector.conversion
|
||||||
|
import re
|
||||||
|
|
||||||
|
from django.db.backends import *
|
||||||
|
from django.db.backends.mysql.client import DatabaseClient
|
||||||
|
from django.db.backends.mysql.creation import DatabaseCreation
|
||||||
|
from django.db.backends.mysql.introspection import DatabaseIntrospection
|
||||||
|
from django.db.backends.mysql.validation import DatabaseValidation
|
||||||
|
from django.utils.safestring import SafeString, SafeUnicode
|
||||||
|
|
||||||
|
# Raise exceptions for database warnings if DEBUG is on
|
||||||
|
from django.conf import settings
|
||||||
|
if settings.DEBUG:
|
||||||
|
from warnings import filterwarnings
|
||||||
|
filterwarnings("error", category=Database.Warning)
|
||||||
|
|
||||||
|
DatabaseError = Database.DatabaseError
|
||||||
|
IntegrityError = Database.IntegrityError
|
||||||
|
|
||||||
|
|
||||||
|
class DjangoMySQLConverter(Database.conversion.MySQLConverter):
|
||||||
|
pass
|
||||||
|
"""
|
||||||
|
def _TIME_to_python(self, v, dsc=None):
|
||||||
|
return util.typecast_time(v)
|
||||||
|
|
||||||
|
def _decimal(self, v, desc=None):
|
||||||
|
return util.typecast_decimal(v)
|
||||||
|
"""
|
||||||
|
# This should match the numerical portion of the version numbers (we can treat
|
||||||
|
# versions like 5.0.24 and 5.0.24a as the same). Based on the list of version
|
||||||
|
# at http://dev.mysql.com/doc/refman/4.1/en/news.html and
|
||||||
|
# http://dev.mysql.com/doc/refman/5.0/en/news.html .
|
||||||
|
server_version_re = re.compile(r'(\d{1,2})\.(\d{1,2})\.(\d{1,2})')
|
||||||
|
|
||||||
|
# MySQLdb-1.2.1 and newer automatically makes use of SHOW WARNINGS on
|
||||||
|
# MySQL-4.1 and newer, so the MysqlDebugWrapper is unnecessary. Since the
|
||||||
|
# point is to raise Warnings as exceptions, this can be done with the Python
|
||||||
|
# warning module, and this is setup when the connection is created, and the
|
||||||
|
# standard util.CursorDebugWrapper can be used. Also, using sql_mode
|
||||||
|
# TRADITIONAL will automatically cause most warnings to be treated as errors.
|
||||||
|
|
||||||
|
class DatabaseFeatures(BaseDatabaseFeatures):
|
||||||
|
autoindexes_primary_keys = False
|
||||||
|
inline_fk_references = False
|
||||||
|
|
||||||
|
class DatabaseOperations(BaseDatabaseOperations):
|
||||||
|
def date_extract_sql(self, lookup_type, field_name):
|
||||||
|
# http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html
|
||||||
|
return "EXTRACT(%s FROM %s)" % (lookup_type.upper(), field_name)
|
||||||
|
|
||||||
|
def date_trunc_sql(self, lookup_type, field_name):
|
||||||
|
fields = ['year', 'month', 'day', 'hour', 'minute', 'second']
|
||||||
|
format = ('%%Y-', '%%m', '-%%d', ' %%H:', '%%i', ':%%s') # Use double percents to escape.
|
||||||
|
format_def = ('0000-', '01', '-01', ' 00:', '00', ':00')
|
||||||
|
try:
|
||||||
|
i = fields.index(lookup_type) + 1
|
||||||
|
except ValueError:
|
||||||
|
sql = field_name
|
||||||
|
else:
|
||||||
|
format_str = ''.join([f for f in format[:i]] + [f for f in format_def[i:]])
|
||||||
|
sql = "CAST(DATE_FORMAT(%s, '%s') AS DATETIME)" % (field_name, format_str)
|
||||||
|
return sql
|
||||||
|
|
||||||
|
def drop_foreignkey_sql(self):
|
||||||
|
return "DROP FOREIGN KEY"
|
||||||
|
|
||||||
|
def fulltext_search_sql(self, field_name):
|
||||||
|
return 'MATCH (%s) AGAINST (%%s IN BOOLEAN MODE)' % field_name
|
||||||
|
|
||||||
|
def limit_offset_sql(self, limit, offset=None):
|
||||||
|
# 'LIMIT 20,40'
|
||||||
|
sql = "LIMIT "
|
||||||
|
if offset and offset != 0:
|
||||||
|
sql += "%s," % offset
|
||||||
|
return sql + str(limit)
|
||||||
|
|
||||||
|
def quote_name(self, name):
|
||||||
|
if name.startswith("`") and name.endswith("`"):
|
||||||
|
return name # Quoting once is enough.
|
||||||
|
return "`%s`" % name
|
||||||
|
|
||||||
|
def random_function_sql(self):
|
||||||
|
return 'RAND()'
|
||||||
|
|
||||||
|
def sql_flush(self, style, tables, sequences):
|
||||||
|
# NB: The generated SQL below is specific to MySQL
|
||||||
|
# 'TRUNCATE x;', 'TRUNCATE y;', 'TRUNCATE z;'... style SQL statements
|
||||||
|
# to clear all tables of all data
|
||||||
|
if tables:
|
||||||
|
sql = ['SET FOREIGN_KEY_CHECKS = 0;']
|
||||||
|
for table in tables:
|
||||||
|
sql.append('%s %s;' % (style.SQL_KEYWORD('TRUNCATE'), style.SQL_FIELD(self.quote_name(table))))
|
||||||
|
sql.append('SET FOREIGN_KEY_CHECKS = 1;')
|
||||||
|
|
||||||
|
# 'ALTER TABLE table AUTO_INCREMENT = 1;'... style SQL statements
|
||||||
|
# to reset sequence indices
|
||||||
|
sql.extend(["%s %s %s %s %s;" % \
|
||||||
|
(style.SQL_KEYWORD('ALTER'),
|
||||||
|
style.SQL_KEYWORD('TABLE'),
|
||||||
|
style.SQL_TABLE(self.quote_name(sequence['table'])),
|
||||||
|
style.SQL_KEYWORD('AUTO_INCREMENT'),
|
||||||
|
style.SQL_FIELD('= 1'),
|
||||||
|
) for sequence in sequences])
|
||||||
|
return sql
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
def value_to_db_datetime(self, value):
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# MySQL doesn't support tz-aware datetimes
|
||||||
|
if value.tzinfo is not None:
|
||||||
|
raise ValueError("MySQL backend does not support timezone-aware datetimes.")
|
||||||
|
|
||||||
|
# MySQL doesn't support microseconds
|
||||||
|
return unicode(value.replace(microsecond=0))
|
||||||
|
|
||||||
|
def value_to_db_time(self, value):
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# MySQL doesn't support tz-aware datetimes
|
||||||
|
if value.tzinfo is not None:
|
||||||
|
raise ValueError("MySQL backend does not support timezone-aware datetimes.")
|
||||||
|
|
||||||
|
# MySQL doesn't support microseconds
|
||||||
|
return unicode(value.replace(microsecond=0))
|
||||||
|
|
||||||
|
def year_lookup_bounds(self, value):
|
||||||
|
# Again, no microseconds
|
||||||
|
first = '%s-01-01 00:00:00'
|
||||||
|
second = '%s-12-31 23:59:59.99'
|
||||||
|
return [first % value, second % value]
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseWrapper(BaseDatabaseWrapper):
|
||||||
|
|
||||||
|
operators = {
|
||||||
|
'exact': '= %s',
|
||||||
|
'iexact': 'LIKE %s',
|
||||||
|
'contains': 'LIKE BINARY %s',
|
||||||
|
'icontains': 'LIKE %s',
|
||||||
|
'regex': 'REGEXP BINARY %s',
|
||||||
|
'iregex': 'REGEXP %s',
|
||||||
|
'gt': '> %s',
|
||||||
|
'gte': '>= %s',
|
||||||
|
'lt': '< %s',
|
||||||
|
'lte': '<= %s',
|
||||||
|
'startswith': 'LIKE BINARY %s',
|
||||||
|
'endswith': 'LIKE BINARY %s',
|
||||||
|
'istartswith': 'LIKE %s',
|
||||||
|
'iendswith': 'LIKE %s',
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super(DatabaseWrapper, self).__init__(**kwargs)
|
||||||
|
self.server_version = None
|
||||||
|
|
||||||
|
self.features = DatabaseFeatures()
|
||||||
|
self.ops = DatabaseOperations()
|
||||||
|
self.client = DatabaseClient()
|
||||||
|
self.creation = DatabaseCreation(self)
|
||||||
|
self.introspection = DatabaseIntrospection(self)
|
||||||
|
self.validation = DatabaseValidation()
|
||||||
|
|
||||||
|
def _valid_connection(self):
|
||||||
|
if self.connection is not None:
|
||||||
|
try:
|
||||||
|
self.connection.ping()
|
||||||
|
return True
|
||||||
|
except DatabaseError:
|
||||||
|
self.connection.close()
|
||||||
|
self.connection = None
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _cursor(self, settings):
|
||||||
|
if not self._valid_connection():
|
||||||
|
kwargs = {
|
||||||
|
#'conv': django_conversions,
|
||||||
|
'charset': 'utf8',
|
||||||
|
'use_unicode': True,
|
||||||
|
}
|
||||||
|
if settings.DATABASE_USER:
|
||||||
|
kwargs['user'] = settings.DATABASE_USER
|
||||||
|
if settings.DATABASE_NAME:
|
||||||
|
kwargs['db'] = settings.DATABASE_NAME
|
||||||
|
if settings.DATABASE_PASSWORD:
|
||||||
|
kwargs['passwd'] = settings.DATABASE_PASSWORD
|
||||||
|
if settings.DATABASE_HOST.startswith('/'):
|
||||||
|
kwargs['unix_socket'] = settings.DATABASE_HOST
|
||||||
|
elif settings.DATABASE_HOST:
|
||||||
|
kwargs['host'] = settings.DATABASE_HOST
|
||||||
|
if settings.DATABASE_PORT:
|
||||||
|
kwargs['port'] = int(settings.DATABASE_PORT)
|
||||||
|
kwargs.update(self.options)
|
||||||
|
self.connection = Database.connect(**kwargs)
|
||||||
|
self.connection.set_converter_class(DjangoMySQLConverter)
|
||||||
|
cursor = self.connection.cursor()
|
||||||
|
return cursor
|
||||||
|
|
||||||
|
def _rollback(self):
|
||||||
|
try:
|
||||||
|
BaseDatabaseWrapper._rollback(self)
|
||||||
|
except Database.NotSupportedError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_server_version(self):
|
||||||
|
if not self.server_version:
|
||||||
|
if not self._valid_connection():
|
||||||
|
self.cursor()
|
||||||
|
self.server_version = self.connection.get_server_version()
|
||||||
|
#m = server_version_re.match(self.connection.get_server_version())
|
||||||
|
#if not m:
|
||||||
|
# raise Exception('Unable to determine MySQL version from version string %r' % self.connection.get_server_version())
|
||||||
|
#self.server_version = tuple([int(x) for x in m.groups()])
|
||||||
|
return self.server_version
|
|
@ -0,0 +1,27 @@
|
||||||
|
from django.conf import settings
|
||||||
|
import os
|
||||||
|
|
||||||
|
def runshell():
|
||||||
|
args = ['']
|
||||||
|
db = settings.DATABASE_OPTIONS.get('db', settings.DATABASE_NAME)
|
||||||
|
user = settings.DATABASE_OPTIONS.get('user', settings.DATABASE_USER)
|
||||||
|
passwd = settings.DATABASE_OPTIONS.get('passwd', settings.DATABASE_PASSWORD)
|
||||||
|
host = settings.DATABASE_OPTIONS.get('host', settings.DATABASE_HOST)
|
||||||
|
port = settings.DATABASE_OPTIONS.get('port', settings.DATABASE_PORT)
|
||||||
|
defaults_file = settings.DATABASE_OPTIONS.get('read_default_file')
|
||||||
|
# Seems to be no good way to set sql_mode with CLI
|
||||||
|
|
||||||
|
if defaults_file:
|
||||||
|
args += ["--defaults-file=%s" % defaults_file]
|
||||||
|
if user:
|
||||||
|
args += ["--user=%s" % user]
|
||||||
|
if passwd:
|
||||||
|
args += ["--password=%s" % passwd]
|
||||||
|
if host:
|
||||||
|
args += ["--host=%s" % host]
|
||||||
|
if port:
|
||||||
|
args += ["--port=%s" % port]
|
||||||
|
if db:
|
||||||
|
args += [db]
|
||||||
|
|
||||||
|
os.execvp('mysql', args)
|
|
@ -0,0 +1,29 @@
|
||||||
|
# This dictionary maps Field objects to their associated MySQL column
|
||||||
|
# types, as strings. Column-type strings can contain format strings; they'll
|
||||||
|
# be interpolated against the values of Field.__dict__ before being output.
|
||||||
|
# If a column type is set to None, it won't be included in the output.
|
||||||
|
DATA_TYPES = {
|
||||||
|
'AutoField': 'integer AUTO_INCREMENT',
|
||||||
|
'BooleanField': 'bool',
|
||||||
|
'CharField': 'varchar(%(max_length)s)',
|
||||||
|
'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
|
||||||
|
'DateField': 'date',
|
||||||
|
'DateTimeField': 'datetime',
|
||||||
|
'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
|
||||||
|
'FileField': 'varchar(%(max_length)s)',
|
||||||
|
'FilePathField': 'varchar(%(max_length)s)',
|
||||||
|
'FloatField': 'double precision',
|
||||||
|
'ImageField': 'varchar(%(max_length)s)',
|
||||||
|
'IntegerField': 'integer',
|
||||||
|
'IPAddressField': 'char(15)',
|
||||||
|
'NullBooleanField': 'bool',
|
||||||
|
'OneToOneField': 'integer',
|
||||||
|
'PhoneNumberField': 'varchar(20)',
|
||||||
|
'PositiveIntegerField': 'integer UNSIGNED',
|
||||||
|
'PositiveSmallIntegerField': 'smallint UNSIGNED',
|
||||||
|
'SlugField': 'varchar(%(max_length)s)',
|
||||||
|
'SmallIntegerField': 'smallint',
|
||||||
|
'TextField': 'longtext',
|
||||||
|
'TimeField': 'time',
|
||||||
|
'USStateField': 'varchar(2)',
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
from base import DatabaseOperations
|
||||||
|
from mysql.connector.errors import ProgrammingError, OperationalError
|
||||||
|
from mysql.connector.constants import FieldType
|
||||||
|
import re
|
||||||
|
|
||||||
|
quote_name = DatabaseOperations().quote_name
|
||||||
|
foreign_key_re = re.compile(r"\sCONSTRAINT `[^`]*` FOREIGN KEY \(`([^`]*)`\) REFERENCES `([^`]*)` \(`([^`]*)`\)")
|
||||||
|
|
||||||
|
def get_table_list(cursor):
|
||||||
|
"Returns a list of table names in the current database."
|
||||||
|
cursor.execute("SHOW TABLES")
|
||||||
|
return [row[0] for row in cursor.fetchall()]
|
||||||
|
|
||||||
|
def get_table_description(cursor, table_name):
|
||||||
|
"Returns a description of the table, with the DB-API cursor.description interface."
|
||||||
|
cursor.execute("SELECT * FROM %s LIMIT 1" % quote_name(table_name))
|
||||||
|
return cursor.description
|
||||||
|
|
||||||
|
def _name_to_index(cursor, table_name):
|
||||||
|
"""
|
||||||
|
Returns a dictionary of {field_name: field_index} for the given table.
|
||||||
|
Indexes are 0-based.
|
||||||
|
"""
|
||||||
|
return dict([(d[0], i) for i, d in enumerate(get_table_description(cursor, table_name))])
|
||||||
|
|
||||||
|
def get_relations(cursor, table_name):
|
||||||
|
"""
|
||||||
|
Returns a dictionary of {field_index: (field_index_other_table, other_table)}
|
||||||
|
representing all relationships to the given table. Indexes are 0-based.
|
||||||
|
"""
|
||||||
|
my_field_dict = _name_to_index(cursor, table_name)
|
||||||
|
constraints = []
|
||||||
|
relations = {}
|
||||||
|
try:
|
||||||
|
# This should work for MySQL 5.0.
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT column_name, referenced_table_name, referenced_column_name
|
||||||
|
FROM information_schema.key_column_usage
|
||||||
|
WHERE table_name = %s
|
||||||
|
AND table_schema = DATABASE()
|
||||||
|
AND referenced_table_name IS NOT NULL
|
||||||
|
AND referenced_column_name IS NOT NULL""", [table_name])
|
||||||
|
constraints.extend(cursor.fetchall())
|
||||||
|
except (ProgrammingError, OperationalError):
|
||||||
|
# Fall back to "SHOW CREATE TABLE", for previous MySQL versions.
|
||||||
|
# Go through all constraints and save the equal matches.
|
||||||
|
cursor.execute("SHOW CREATE TABLE %s" % quote_name(table_name))
|
||||||
|
for row in cursor.fetchall():
|
||||||
|
pos = 0
|
||||||
|
while True:
|
||||||
|
match = foreign_key_re.search(row[1], pos)
|
||||||
|
if match == None:
|
||||||
|
break
|
||||||
|
pos = match.end()
|
||||||
|
constraints.append(match.groups())
|
||||||
|
|
||||||
|
for my_fieldname, other_table, other_field in constraints:
|
||||||
|
other_field_index = _name_to_index(cursor, other_table)[other_field]
|
||||||
|
my_field_index = my_field_dict[my_fieldname]
|
||||||
|
relations[my_field_index] = (other_field_index, other_table)
|
||||||
|
|
||||||
|
return relations
|
||||||
|
|
||||||
|
def get_indexes(cursor, table_name):
|
||||||
|
"""
|
||||||
|
Returns a dictionary of fieldname -> infodict for the given table,
|
||||||
|
where each infodict is in the format:
|
||||||
|
{'primary_key': boolean representing whether it's the primary key,
|
||||||
|
'unique': boolean representing whether it's a unique index}
|
||||||
|
"""
|
||||||
|
cursor.execute("SHOW INDEX FROM %s" % quote_name(table_name))
|
||||||
|
indexes = {}
|
||||||
|
for row in cursor.fetchall():
|
||||||
|
indexes[row[4]] = {'primary_key': (row[2] == 'PRIMARY'), 'unique': not bool(row[1])}
|
||||||
|
return indexes
|
||||||
|
|
||||||
|
DATA_TYPES_REVERSE = {
|
||||||
|
FieldType.BLOB: 'TextField',
|
||||||
|
FieldType.STRING: 'CharField',
|
||||||
|
FieldType.DECIMAL: 'DecimalField',
|
||||||
|
FieldType.DATE: 'DateField',
|
||||||
|
FieldType.DATETIME: 'DateTimeField',
|
||||||
|
FieldType.DOUBLE: 'FloatField',
|
||||||
|
FieldType.FLOAT: 'FloatField',
|
||||||
|
FieldType.INT24: 'IntegerField',
|
||||||
|
FieldType.LONG: 'IntegerField',
|
||||||
|
FieldType.LONGLONG: 'IntegerField',
|
||||||
|
FieldType.SHORT: 'IntegerField',
|
||||||
|
FieldType.STRING: 'CharField',
|
||||||
|
FieldType.TIMESTAMP: 'DateTimeField',
|
||||||
|
FieldType.TINY: 'IntegerField',
|
||||||
|
FieldType.TINY_BLOB: 'TextField',
|
||||||
|
FieldType.MEDIUM_BLOB: 'TextField',
|
||||||
|
FieldType.LONG_BLOB: 'TextField',
|
||||||
|
FieldType.VAR_STRING: 'CharField',
|
||||||
|
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,20 @@
|
||||||
|
"""
|
||||||
|
Connector/Python, native MySQL driver written in Python.
|
||||||
|
Copyright 2009 Sun Microsystems, Inc. All rights reserved. Use is subject to license terms.
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
"""
|
||||||
|
# This file should be automatically generated on each release
|
||||||
|
version = (0, 0, 1, 'alpha', '')
|
Binary file not shown.
|
@ -0,0 +1,238 @@
|
||||||
|
"""
|
||||||
|
MySQL database backend for Django using MySQL Connector/Python.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util
|
||||||
|
try:
|
||||||
|
import mysql.connector as Database
|
||||||
|
except ImportError, e:
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
raise ImproperlyConfigured("Error loading MySQLdb module: %s" % e)
|
||||||
|
|
||||||
|
# We want version (1, 2, 1, 'final', 2) or later. We can't just use
|
||||||
|
# lexicographic ordering in this check because then (1, 2, 1, 'gamma')
|
||||||
|
# inadvertently passes the version test.
|
||||||
|
version = Database.__version__
|
||||||
|
if ( version[:3] < (0, 0, 2) ):
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
raise ImproperlyConfigured("MySQL Connector/Python 0.0.2 or newer is required; you have %s" % Database.__version__)
|
||||||
|
|
||||||
|
import mysql.connector.conversion
|
||||||
|
import re
|
||||||
|
|
||||||
|
from django.db.backends import *
|
||||||
|
from django.db.backends.mysql.client import DatabaseClient
|
||||||
|
from django.db.backends.mysql.creation import DatabaseCreation
|
||||||
|
from django.db.backends.mysql.introspection import DatabaseIntrospection
|
||||||
|
from django.db.backends.mysql.validation import DatabaseValidation
|
||||||
|
from django.utils.safestring import SafeString, SafeUnicode
|
||||||
|
|
||||||
|
# Raise exceptions for database warnings if DEBUG is on
|
||||||
|
from django.conf import settings
|
||||||
|
if settings.DEBUG:
|
||||||
|
from warnings import filterwarnings
|
||||||
|
filterwarnings("error", category=Database.Warning)
|
||||||
|
|
||||||
|
DatabaseError = Database.DatabaseError
|
||||||
|
IntegrityError = Database.IntegrityError
|
||||||
|
|
||||||
|
|
||||||
|
class DjangoMySQLConverter(Database.conversion.MySQLConverter):
|
||||||
|
pass
|
||||||
|
"""
|
||||||
|
def _TIME_to_python(self, v, dsc=None):
|
||||||
|
return util.typecast_time(v)
|
||||||
|
|
||||||
|
def _decimal(self, v, desc=None):
|
||||||
|
return util.typecast_decimal(v)
|
||||||
|
"""
|
||||||
|
# This should match the numerical portion of the version numbers (we can treat
|
||||||
|
# versions like 5.0.24 and 5.0.24a as the same). Based on the list of version
|
||||||
|
# at http://dev.mysql.com/doc/refman/4.1/en/news.html and
|
||||||
|
# http://dev.mysql.com/doc/refman/5.0/en/news.html .
|
||||||
|
server_version_re = re.compile(r'(\d{1,2})\.(\d{1,2})\.(\d{1,2})')
|
||||||
|
|
||||||
|
# MySQLdb-1.2.1 and newer automatically makes use of SHOW WARNINGS on
|
||||||
|
# MySQL-4.1 and newer, so the MysqlDebugWrapper is unnecessary. Since the
|
||||||
|
# point is to raise Warnings as exceptions, this can be done with the Python
|
||||||
|
# warning module, and this is setup when the connection is created, and the
|
||||||
|
# standard util.CursorDebugWrapper can be used. Also, using sql_mode
|
||||||
|
# TRADITIONAL will automatically cause most warnings to be treated as errors.
|
||||||
|
|
||||||
|
class DatabaseFeatures(BaseDatabaseFeatures):
|
||||||
|
autoindexes_primary_keys = False
|
||||||
|
inline_fk_references = False
|
||||||
|
|
||||||
|
class DatabaseOperations(BaseDatabaseOperations):
|
||||||
|
def date_extract_sql(self, lookup_type, field_name):
|
||||||
|
# http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html
|
||||||
|
return "EXTRACT(%s FROM %s)" % (lookup_type.upper(), field_name)
|
||||||
|
|
||||||
|
def date_trunc_sql(self, lookup_type, field_name):
|
||||||
|
fields = ['year', 'month', 'day', 'hour', 'minute', 'second']
|
||||||
|
format = ('%%Y-', '%%m', '-%%d', ' %%H:', '%%i', ':%%s') # Use double percents to escape.
|
||||||
|
format_def = ('0000-', '01', '-01', ' 00:', '00', ':00')
|
||||||
|
try:
|
||||||
|
i = fields.index(lookup_type) + 1
|
||||||
|
except ValueError:
|
||||||
|
sql = field_name
|
||||||
|
else:
|
||||||
|
format_str = ''.join([f for f in format[:i]] + [f for f in format_def[i:]])
|
||||||
|
sql = "CAST(DATE_FORMAT(%s, '%s') AS DATETIME)" % (field_name, format_str)
|
||||||
|
return sql
|
||||||
|
|
||||||
|
def drop_foreignkey_sql(self):
|
||||||
|
return "DROP FOREIGN KEY"
|
||||||
|
|
||||||
|
def fulltext_search_sql(self, field_name):
|
||||||
|
return 'MATCH (%s) AGAINST (%%s IN BOOLEAN MODE)' % field_name
|
||||||
|
|
||||||
|
def limit_offset_sql(self, limit, offset=None):
|
||||||
|
# 'LIMIT 20,40'
|
||||||
|
sql = "LIMIT "
|
||||||
|
if offset and offset != 0:
|
||||||
|
sql += "%s," % offset
|
||||||
|
return sql + str(limit)
|
||||||
|
|
||||||
|
def quote_name(self, name):
|
||||||
|
if name.startswith("`") and name.endswith("`"):
|
||||||
|
return name # Quoting once is enough.
|
||||||
|
return "`%s`" % name
|
||||||
|
|
||||||
|
def random_function_sql(self):
|
||||||
|
return 'RAND()'
|
||||||
|
|
||||||
|
def sql_flush(self, style, tables, sequences):
|
||||||
|
# NB: The generated SQL below is specific to MySQL
|
||||||
|
# 'TRUNCATE x;', 'TRUNCATE y;', 'TRUNCATE z;'... style SQL statements
|
||||||
|
# to clear all tables of all data
|
||||||
|
if tables:
|
||||||
|
sql = ['SET FOREIGN_KEY_CHECKS = 0;']
|
||||||
|
for table in tables:
|
||||||
|
sql.append('%s %s;' % (style.SQL_KEYWORD('TRUNCATE'), style.SQL_FIELD(self.quote_name(table))))
|
||||||
|
sql.append('SET FOREIGN_KEY_CHECKS = 1;')
|
||||||
|
|
||||||
|
# 'ALTER TABLE table AUTO_INCREMENT = 1;'... style SQL statements
|
||||||
|
# to reset sequence indices
|
||||||
|
sql.extend(["%s %s %s %s %s;" % \
|
||||||
|
(style.SQL_KEYWORD('ALTER'),
|
||||||
|
style.SQL_KEYWORD('TABLE'),
|
||||||
|
style.SQL_TABLE(self.quote_name(sequence['table'])),
|
||||||
|
style.SQL_KEYWORD('AUTO_INCREMENT'),
|
||||||
|
style.SQL_FIELD('= 1'),
|
||||||
|
) for sequence in sequences])
|
||||||
|
return sql
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
def value_to_db_datetime(self, value):
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# MySQL doesn't support tz-aware datetimes
|
||||||
|
if value.tzinfo is not None:
|
||||||
|
raise ValueError("MySQL backend does not support timezone-aware datetimes.")
|
||||||
|
|
||||||
|
# MySQL doesn't support microseconds
|
||||||
|
return unicode(value.replace(microsecond=0))
|
||||||
|
|
||||||
|
def value_to_db_time(self, value):
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# MySQL doesn't support tz-aware datetimes
|
||||||
|
if value.tzinfo is not None:
|
||||||
|
raise ValueError("MySQL backend does not support timezone-aware datetimes.")
|
||||||
|
|
||||||
|
# MySQL doesn't support microseconds
|
||||||
|
return unicode(value.replace(microsecond=0))
|
||||||
|
|
||||||
|
def year_lookup_bounds(self, value):
|
||||||
|
# Again, no microseconds
|
||||||
|
first = '%s-01-01 00:00:00'
|
||||||
|
second = '%s-12-31 23:59:59.99'
|
||||||
|
return [first % value, second % value]
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseWrapper(BaseDatabaseWrapper):
|
||||||
|
|
||||||
|
operators = {
|
||||||
|
'exact': '= %s',
|
||||||
|
'iexact': 'LIKE %s',
|
||||||
|
'contains': 'LIKE BINARY %s',
|
||||||
|
'icontains': 'LIKE %s',
|
||||||
|
'regex': 'REGEXP BINARY %s',
|
||||||
|
'iregex': 'REGEXP %s',
|
||||||
|
'gt': '> %s',
|
||||||
|
'gte': '>= %s',
|
||||||
|
'lt': '< %s',
|
||||||
|
'lte': '<= %s',
|
||||||
|
'startswith': 'LIKE BINARY %s',
|
||||||
|
'endswith': 'LIKE BINARY %s',
|
||||||
|
'istartswith': 'LIKE %s',
|
||||||
|
'iendswith': 'LIKE %s',
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super(DatabaseWrapper, self).__init__(**kwargs)
|
||||||
|
self.server_version = None
|
||||||
|
|
||||||
|
self.features = DatabaseFeatures()
|
||||||
|
self.ops = DatabaseOperations()
|
||||||
|
self.client = DatabaseClient()
|
||||||
|
self.creation = DatabaseCreation(self)
|
||||||
|
self.introspection = DatabaseIntrospection(self)
|
||||||
|
self.validation = DatabaseValidation()
|
||||||
|
|
||||||
|
def _valid_connection(self):
|
||||||
|
if self.connection is not None:
|
||||||
|
try:
|
||||||
|
self.connection.ping()
|
||||||
|
return True
|
||||||
|
except DatabaseError:
|
||||||
|
self.connection.close()
|
||||||
|
self.connection = None
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _cursor(self, settings):
|
||||||
|
if not self._valid_connection():
|
||||||
|
kwargs = {
|
||||||
|
#'conv': django_conversions,
|
||||||
|
'charset': 'utf8',
|
||||||
|
'use_unicode': True,
|
||||||
|
}
|
||||||
|
if settings.DATABASE_USER:
|
||||||
|
kwargs['user'] = settings.DATABASE_USER
|
||||||
|
if settings.DATABASE_NAME:
|
||||||
|
kwargs['db'] = settings.DATABASE_NAME
|
||||||
|
if settings.DATABASE_PASSWORD:
|
||||||
|
kwargs['passwd'] = settings.DATABASE_PASSWORD
|
||||||
|
if settings.DATABASE_HOST.startswith('/'):
|
||||||
|
kwargs['unix_socket'] = settings.DATABASE_HOST
|
||||||
|
elif settings.DATABASE_HOST:
|
||||||
|
kwargs['host'] = settings.DATABASE_HOST
|
||||||
|
if settings.DATABASE_PORT:
|
||||||
|
kwargs['port'] = int(settings.DATABASE_PORT)
|
||||||
|
kwargs.update(self.options)
|
||||||
|
self.connection = Database.connect(**kwargs)
|
||||||
|
self.connection.set_converter_class(DjangoMySQLConverter)
|
||||||
|
cursor = self.connection.cursor()
|
||||||
|
return cursor
|
||||||
|
|
||||||
|
def _rollback(self):
|
||||||
|
try:
|
||||||
|
BaseDatabaseWrapper._rollback(self)
|
||||||
|
except Database.NotSupportedError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_server_version(self):
|
||||||
|
if not self.server_version:
|
||||||
|
if not self._valid_connection():
|
||||||
|
self.cursor()
|
||||||
|
self.server_version = self.connection.get_server_version()
|
||||||
|
#m = server_version_re.match(self.connection.get_server_version())
|
||||||
|
#if not m:
|
||||||
|
# raise Exception('Unable to determine MySQL version from version string %r' % self.connection.get_server_version())
|
||||||
|
#self.server_version = tuple([int(x) for x in m.groups()])
|
||||||
|
return self.server_version
|
|
@ -0,0 +1,27 @@
|
||||||
|
from django.conf import settings
|
||||||
|
import os
|
||||||
|
|
||||||
|
def runshell():
|
||||||
|
args = ['']
|
||||||
|
db = settings.DATABASE_OPTIONS.get('db', settings.DATABASE_NAME)
|
||||||
|
user = settings.DATABASE_OPTIONS.get('user', settings.DATABASE_USER)
|
||||||
|
passwd = settings.DATABASE_OPTIONS.get('passwd', settings.DATABASE_PASSWORD)
|
||||||
|
host = settings.DATABASE_OPTIONS.get('host', settings.DATABASE_HOST)
|
||||||
|
port = settings.DATABASE_OPTIONS.get('port', settings.DATABASE_PORT)
|
||||||
|
defaults_file = settings.DATABASE_OPTIONS.get('read_default_file')
|
||||||
|
# Seems to be no good way to set sql_mode with CLI
|
||||||
|
|
||||||
|
if defaults_file:
|
||||||
|
args += ["--defaults-file=%s" % defaults_file]
|
||||||
|
if user:
|
||||||
|
args += ["--user=%s" % user]
|
||||||
|
if passwd:
|
||||||
|
args += ["--password=%s" % passwd]
|
||||||
|
if host:
|
||||||
|
args += ["--host=%s" % host]
|
||||||
|
if port:
|
||||||
|
args += ["--port=%s" % port]
|
||||||
|
if db:
|
||||||
|
args += [db]
|
||||||
|
|
||||||
|
os.execvp('mysql', args)
|
|
@ -0,0 +1,29 @@
|
||||||
|
# This dictionary maps Field objects to their associated MySQL column
|
||||||
|
# types, as strings. Column-type strings can contain format strings; they'll
|
||||||
|
# be interpolated against the values of Field.__dict__ before being output.
|
||||||
|
# If a column type is set to None, it won't be included in the output.
|
||||||
|
DATA_TYPES = {
|
||||||
|
'AutoField': 'integer AUTO_INCREMENT',
|
||||||
|
'BooleanField': 'bool',
|
||||||
|
'CharField': 'varchar(%(max_length)s)',
|
||||||
|
'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
|
||||||
|
'DateField': 'date',
|
||||||
|
'DateTimeField': 'datetime',
|
||||||
|
'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
|
||||||
|
'FileField': 'varchar(%(max_length)s)',
|
||||||
|
'FilePathField': 'varchar(%(max_length)s)',
|
||||||
|
'FloatField': 'double precision',
|
||||||
|
'ImageField': 'varchar(%(max_length)s)',
|
||||||
|
'IntegerField': 'integer',
|
||||||
|
'IPAddressField': 'char(15)',
|
||||||
|
'NullBooleanField': 'bool',
|
||||||
|
'OneToOneField': 'integer',
|
||||||
|
'PhoneNumberField': 'varchar(20)',
|
||||||
|
'PositiveIntegerField': 'integer UNSIGNED',
|
||||||
|
'PositiveSmallIntegerField': 'smallint UNSIGNED',
|
||||||
|
'SlugField': 'varchar(%(max_length)s)',
|
||||||
|
'SmallIntegerField': 'smallint',
|
||||||
|
'TextField': 'longtext',
|
||||||
|
'TimeField': 'time',
|
||||||
|
'USStateField': 'varchar(2)',
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
from base import DatabaseOperations
|
||||||
|
from mysql.connector.errors import ProgrammingError, OperationalError
|
||||||
|
from mysql.connector.constants import FieldType
|
||||||
|
import re
|
||||||
|
|
||||||
|
quote_name = DatabaseOperations().quote_name
|
||||||
|
foreign_key_re = re.compile(r"\sCONSTRAINT `[^`]*` FOREIGN KEY \(`([^`]*)`\) REFERENCES `([^`]*)` \(`([^`]*)`\)")
|
||||||
|
|
||||||
|
def get_table_list(cursor):
|
||||||
|
"Returns a list of table names in the current database."
|
||||||
|
cursor.execute("SHOW TABLES")
|
||||||
|
return [row[0] for row in cursor.fetchall()]
|
||||||
|
|
||||||
|
def get_table_description(cursor, table_name):
|
||||||
|
"Returns a description of the table, with the DB-API cursor.description interface."
|
||||||
|
cursor.execute("SELECT * FROM %s LIMIT 1" % quote_name(table_name))
|
||||||
|
return cursor.description
|
||||||
|
|
||||||
|
def _name_to_index(cursor, table_name):
|
||||||
|
"""
|
||||||
|
Returns a dictionary of {field_name: field_index} for the given table.
|
||||||
|
Indexes are 0-based.
|
||||||
|
"""
|
||||||
|
return dict([(d[0], i) for i, d in enumerate(get_table_description(cursor, table_name))])
|
||||||
|
|
||||||
|
def get_relations(cursor, table_name):
|
||||||
|
"""
|
||||||
|
Returns a dictionary of {field_index: (field_index_other_table, other_table)}
|
||||||
|
representing all relationships to the given table. Indexes are 0-based.
|
||||||
|
"""
|
||||||
|
my_field_dict = _name_to_index(cursor, table_name)
|
||||||
|
constraints = []
|
||||||
|
relations = {}
|
||||||
|
try:
|
||||||
|
# This should work for MySQL 5.0.
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT column_name, referenced_table_name, referenced_column_name
|
||||||
|
FROM information_schema.key_column_usage
|
||||||
|
WHERE table_name = %s
|
||||||
|
AND table_schema = DATABASE()
|
||||||
|
AND referenced_table_name IS NOT NULL
|
||||||
|
AND referenced_column_name IS NOT NULL""", [table_name])
|
||||||
|
constraints.extend(cursor.fetchall())
|
||||||
|
except (ProgrammingError, OperationalError):
|
||||||
|
# Fall back to "SHOW CREATE TABLE", for previous MySQL versions.
|
||||||
|
# Go through all constraints and save the equal matches.
|
||||||
|
cursor.execute("SHOW CREATE TABLE %s" % quote_name(table_name))
|
||||||
|
for row in cursor.fetchall():
|
||||||
|
pos = 0
|
||||||
|
while True:
|
||||||
|
match = foreign_key_re.search(row[1], pos)
|
||||||
|
if match == None:
|
||||||
|
break
|
||||||
|
pos = match.end()
|
||||||
|
constraints.append(match.groups())
|
||||||
|
|
||||||
|
for my_fieldname, other_table, other_field in constraints:
|
||||||
|
other_field_index = _name_to_index(cursor, other_table)[other_field]
|
||||||
|
my_field_index = my_field_dict[my_fieldname]
|
||||||
|
relations[my_field_index] = (other_field_index, other_table)
|
||||||
|
|
||||||
|
return relations
|
||||||
|
|
||||||
|
def get_indexes(cursor, table_name):
|
||||||
|
"""
|
||||||
|
Returns a dictionary of fieldname -> infodict for the given table,
|
||||||
|
where each infodict is in the format:
|
||||||
|
{'primary_key': boolean representing whether it's the primary key,
|
||||||
|
'unique': boolean representing whether it's a unique index}
|
||||||
|
"""
|
||||||
|
cursor.execute("SHOW INDEX FROM %s" % quote_name(table_name))
|
||||||
|
indexes = {}
|
||||||
|
for row in cursor.fetchall():
|
||||||
|
indexes[row[4]] = {'primary_key': (row[2] == 'PRIMARY'), 'unique': not bool(row[1])}
|
||||||
|
return indexes
|
||||||
|
|
||||||
|
DATA_TYPES_REVERSE = {
|
||||||
|
FieldType.BLOB: 'TextField',
|
||||||
|
FieldType.STRING: 'CharField',
|
||||||
|
FieldType.DECIMAL: 'DecimalField',
|
||||||
|
FieldType.DATE: 'DateField',
|
||||||
|
FieldType.DATETIME: 'DateTimeField',
|
||||||
|
FieldType.DOUBLE: 'FloatField',
|
||||||
|
FieldType.FLOAT: 'FloatField',
|
||||||
|
FieldType.INT24: 'IntegerField',
|
||||||
|
FieldType.LONG: 'IntegerField',
|
||||||
|
FieldType.LONGLONG: 'IntegerField',
|
||||||
|
FieldType.SHORT: 'IntegerField',
|
||||||
|
FieldType.STRING: 'CharField',
|
||||||
|
FieldType.TIMESTAMP: 'DateTimeField',
|
||||||
|
FieldType.TINY: 'IntegerField',
|
||||||
|
FieldType.TINY_BLOB: 'TextField',
|
||||||
|
FieldType.MEDIUM_BLOB: 'TextField',
|
||||||
|
FieldType.LONG_BLOB: 'TextField',
|
||||||
|
FieldType.VAR_STRING: 'CharField',
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Connector/Python, native MySQL driver written in Python.
|
||||||
|
Copyright 2009 Sun Microsystems, Inc. All rights reserved. Use is subject to license terms.
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from distutils.core import setup
|
||||||
|
from mysql.django._version import version as mysql_django_version
|
||||||
|
|
||||||
|
_name = 'Django Database Backend using MySQL Connector/Python'
|
||||||
|
_version = '%d.%d.%d' % mysql_django_version[0:3]
|
||||||
|
_packages = ['mysql','mysql.django']
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name = _name,
|
||||||
|
version = _version,
|
||||||
|
author = 'Geert Vanderkelen',
|
||||||
|
author_email = 'geert.vanderkelen@sun.com',
|
||||||
|
url = 'http://dev.mysql.com/usingmysql/python/',
|
||||||
|
download_url = 'http://dev.mysql.com/downloads/connector/python/',
|
||||||
|
packages = _packages
|
||||||
|
)
|
Loading…
Reference in New Issue