Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# https://editorconfig.org/

root = true

[*]
indent_style = space
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf
charset = utf-8
max_line_length = 119

[*.{yml,yaml}]
indent_size = 2
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@ Thumbs.db
*.egg-info

tests/local_settings.py

# Virtual Env
/venv/
.idea/
151 changes: 151 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
sudo: required
language: python
cache: pip

branches:
only:
- azure-2.1

templates:
mssql: &mssql DB_PACKAGES="" DATABASE_URL="mssql://SA:MyPassword42@localhost:1433/default?isolation_level=read committed&driver=ODBC Driver 17 for SQL Server" DATABASE_URL_OTHER="mssql://SA:MyPassword42@localhost:1433/other?isolation_level=read committed&driver=ODBC Driver 17 for SQL Server"

matrix:
include:
- env: FLAKE8
python: "3.6"
install: pip install flake8==3.7.1
script: flake8

- python: "3.6"
dist: trusty
services: docker
before_install:
- docker pull mcr.microsoft.com/mssql/server:2017-latest-ubuntu
- docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=MyPassword42' -p 1433:1433 -d mcr.microsoft.com/mssql/server:2017-latest-ubuntu
- curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add -
- curl https://packages.microsoft.com/config/ubuntu/14.04/prod.list | sudo tee /etc/apt/sources.list.d/mssql-release.list
- sudo apt-get update
- sudo ACCEPT_EULA=Y apt-get install msodbcsql17
env:
- *mssql

- os: windows
language: sh
python: "3.6"
services: docker
before_install:
- docker pull christianacca/mssql-server-windows-express:1803
- docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=MyPassword42' -p 1433:1433 -d christianacca/mssql-server-windows-express:1803
- wget https://download.microsoft.com/download/E/6/B/E6BFDC7A-5BCD-4C51-9912-635646DA801E/en-US/msodbcsql_17.3.1.1_x64.msi
- powershell "Start-Process msiexec.exe -Wait -ArgumentList '/I msodbcsql_17.3.1.1_x64.msi /qn /norestart IACCEPTMSODBCSQLLICENSETERMS=YES'"
- choco install python3 --version 3.6.6
- export PATH="/c/Python36:/c/Python36/Scripts:$PATH"
env:
- *mssql

install:
- python -m pip install --upgrade pip wheel setuptools
- pip install -e .["tests$DB_PACKAGES"]
- git clone --branch=stable/2.1.x https://github.com/django/django.git "$TRAVIS_BUILD_DIR/../django" --depth=1
- export PYTHONPATH=$PYTHONPATH:$TRAVIS_BUILD_DIR

script:
- cd "$TRAVIS_BUILD_DIR/../django/tests"
- pip install -r requirements/py3.txt
- ./runtests.py --settings=testapp.settings \
aggregation
aggregation_regress
annotations
backends basic
bulk_create constraints
custom_columns
custom_lookups
custom_managers
custom_methods
custom_migration_operations
custom_pk
datatypes
dates
datetimes
db_functions
db_typecasts
db_utils
dbshell
defer
defer_regress
delete
delete_regress
distinct_on_fields
empty
empty_models
expressions
expressions_case
expressions_window
extra_regress
field_deconstruction
field_defaults
field_subclassing
filtered_relation
fixtures
fixtures_model_package
fixtures_regress
force_insert_update
foreign_object
from_db_value
generic_relations
generic_relations_regress
get_earliest_or_latest
get_object_or_404
get_or_create
indexes
inspectdb
introspection
invalid_model_tests
known_related_objects
lookup
m2m_and_m2o
m2m_intermediary
m2m_multiple
m2m_recursive
m2m_regress
m2m_signals
m2m_through
m2m_through_regress
m2o_recursive
managers_regress
many_to_many
many_to_one
many_to_one_null
max_lengths
migrate_signals
migration_test_data_persistance
migrations
migrations2
model_fields
model_indexes
model_options
mutually_referential
nested_foreign_keys
null_fk
null_fk_ordering
null_queries
one_to_one
or_lookups
order_with_respect_to
ordering
pagination
prefetch_related
queries
queryset_pickle
raw_query
reverse_lookup
save_delete_hooks
schema
select_for_update
select_related
select_related_onetoone
select_related_regress
transaction_hooks
transactions
update
update_only_fields
5 changes: 5 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[flake8]
exclude = .git,__pycache__,
# W504 is mutually exclusive with W503
ignore = W504
max-line-length = 119
7 changes: 5 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
except ImportError:
from distutils.core import setup

CLASSIFIERS=[
CLASSIFIERS = [
'Development Status :: 4 - Beta',
'License :: OSI Approved :: BSD License',
'Framework :: Django',
Expand All @@ -26,9 +26,12 @@
license='BSD',
packages=['sql_server', 'sql_server.pyodbc'],
install_requires=[
'Django>=2.2.0,<2.3',
'Django>=2.1.0,<2.2',
'pyodbc>=3.0',
],
extras_require={
'tests': ['dj-database-url==0.5.0'],
},
classifiers=CLASSIFIERS,
keywords='azure django',
)
85 changes: 43 additions & 42 deletions sql_server/pyodbc/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,36 @@
from django.core.exceptions import ImproperlyConfigured
from django import VERSION

if VERSION[:3] < (2,2,0) or VERSION[:2] >= (2,3):
if VERSION[:3] < (2, 1, 0) or VERSION[:2] >= (2, 2):
raise ImproperlyConfigured("Django %d.%d.%d is not supported." % VERSION[:3])

try:
import pyodbc as Database
except ImportError as e:
raise ImproperlyConfigured("Error loading pyodbc module: %s" % e)

from django.utils.version import get_version_tuple
from django.utils.version import get_version_tuple # noqa

pyodbc_ver = get_version_tuple(Database.version)
if pyodbc_ver < (3,0):
if pyodbc_ver < (3, 0):
raise ImproperlyConfigured("pyodbc 3.0 or newer is required; you have %s" % Database.version)

from django.conf import settings
from django.db import NotSupportedError
from django.db.backends.base.base import BaseDatabaseWrapper
from django.db.backends.base.validation import BaseDatabaseValidation
from django.utils.encoding import smart_str
from django.utils.functional import cached_property
from django.utils.timezone import utc
from django.conf import settings # noqa
from django.db import NotSupportedError # noqa
from django.db.backends.base.base import BaseDatabaseWrapper # noqa
from django.utils.encoding import smart_str # noqa
from django.utils.functional import cached_property # noqa

if hasattr(settings, 'DATABASE_CONNECTION_POOLING'):
if not settings.DATABASE_CONNECTION_POOLING:
Database.pooling = False

from .client import DatabaseClient
from .creation import DatabaseCreation
from .features import DatabaseFeatures
from .introspection import DatabaseIntrospection
from .operations import DatabaseOperations
from .schema import DatabaseSchemaEditor
from .client import DatabaseClient # noqa
from .creation import DatabaseCreation # noqa
from .features import DatabaseFeatures # noqa
from .introspection import DatabaseIntrospection # noqa
from .operations import DatabaseOperations # noqa
from .schema import DatabaseSchemaEditor # noqa

EDITION_AZURE_SQL_DB = 5

Expand All @@ -57,6 +55,7 @@ def encode_connection_string(fields):
for k, v in fields.items()
)


def encode_value(v):
"""If the value contains a semicolon, or starts with a left curly brace,
then enclose it in curly braces and escape all right curly braces.
Expand All @@ -65,6 +64,7 @@ def encode_value(v):
return '{%s}' % (v.replace('}', '}}'),)
return v


class DatabaseWrapper(BaseDatabaseWrapper):
vendor = 'microsoft'
display_name = 'SQL Server'
Expand All @@ -73,31 +73,31 @@ class DatabaseWrapper(BaseDatabaseWrapper):
# 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': 'int IDENTITY (1, 1)',
'BigAutoField': 'bigint IDENTITY (1, 1)',
'BigIntegerField': 'bigint',
'BinaryField': 'varbinary(max)',
'BooleanField': 'bit',
'CharField': 'nvarchar(%(max_length)s)',
'DateField': 'date',
'DateTimeField': 'datetime2',
'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
'DurationField': 'bigint',
'FileField': 'nvarchar(%(max_length)s)',
'FilePathField': 'nvarchar(%(max_length)s)',
'FloatField': 'double precision',
'IntegerField': 'int',
'IPAddressField': 'nvarchar(15)',
'AutoField': 'int IDENTITY (1, 1)',
'BigAutoField': 'bigint IDENTITY (1, 1)',
'BigIntegerField': 'bigint',
'BinaryField': 'varbinary(max)',
'BooleanField': 'bit',
'CharField': 'nvarchar(%(max_length)s)',
'DateField': 'date',
'DateTimeField': 'datetime2',
'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
'DurationField': 'bigint',
'FileField': 'nvarchar(%(max_length)s)',
'FilePathField': 'nvarchar(%(max_length)s)',
'FloatField': 'double precision',
'IntegerField': 'int',
'IPAddressField': 'nvarchar(15)',
'GenericIPAddressField': 'nvarchar(39)',
'NullBooleanField': 'bit',
'OneToOneField': 'int',
'NullBooleanField': 'bit',
'OneToOneField': 'int',
'PositiveIntegerField': 'int',
'PositiveSmallIntegerField': 'smallint',
'SlugField': 'nvarchar(%(max_length)s)',
'SlugField': 'nvarchar(%(max_length)s)',
'SmallIntegerField': 'smallint',
'TextField': 'nvarchar(max)',
'TimeField': 'time',
'UUIDField': 'char(32)',
'TextField': 'nvarchar(max)',
'TimeField': 'time',
'UUIDField': 'char(32)',
}
data_type_check_constraints = {
'PositiveIntegerField': '[%(column)s] >= 0',
Expand Down Expand Up @@ -324,7 +324,7 @@ def init_connection_state(self):
if ver < (0, 95):
raise ImproperlyConfigured(
"FreeTDS 0.95 or newer is required.")
except:
except Exception:
# unknown driver version
pass

Expand Down Expand Up @@ -380,7 +380,7 @@ def sql_server_version(self, _known_versions={}):
cursor.execute("SELECT CAST(SERVERPROPERTY('ProductVersion') AS varchar)")
ver = cursor.fetchone()[0]
ver = int(ver.split('.')[0])
if not ver in self._sql_server_versions:
if ver not in self._sql_server_versions:
raise NotSupportedError('SQL Server v%d is not supported.' % ver)
_known_versions[self.alias] = self._sql_server_versions[ver]
return _known_versions[self.alias]
Expand Down Expand Up @@ -419,7 +419,7 @@ def _on_error(self, e):
self.close()
# wait a moment for recovery from network error
time.sleep(self.connection_recovery_interval_msec)
except:
except Exception:
pass
self.connection = None

Expand Down Expand Up @@ -458,14 +458,14 @@ def check_constraints(self, table_names=None):

def disable_constraint_checking(self):
# Azure SQL Database doesn't support sp_msforeachtable
#cursor.execute('EXEC sp_msforeachtable "ALTER TABLE ? NOCHECK CONSTRAINT ALL"')
# cursor.execute('EXEC sp_msforeachtable "ALTER TABLE ? NOCHECK CONSTRAINT ALL"')
if not self.needs_rollback:
self._execute_foreach('ALTER TABLE %s NOCHECK CONSTRAINT ALL')
return not self.needs_rollback

def enable_constraint_checking(self):
# Azure SQL Database doesn't support sp_msforeachtable
#cursor.execute('EXEC sp_msforeachtable "ALTER TABLE ? WITH CHECK CHECK CONSTRAINT ALL"')
# cursor.execute('EXEC sp_msforeachtable "ALTER TABLE ? WITH CHECK CHECK CONSTRAINT ALL"')
if not self.needs_rollback:
self.check_constraints()

Expand All @@ -475,6 +475,7 @@ class CursorWrapper(object):
A wrapper around the pyodbc's cursor that takes in account a) some pyodbc
DB-API 2.0 implementation and b) some common ODBC driver particularities.
"""

def __init__(self, cursor, connection):
self.active = True
self.cursor = cursor
Expand Down
3 changes: 2 additions & 1 deletion sql_server/pyodbc/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from django.db.backends.base.client import BaseDatabaseClient


class DatabaseClient(BaseDatabaseClient):
executable_name = 'sqlcmd'

Expand Down Expand Up @@ -33,7 +34,7 @@ def runshell(self):
if password:
args += ["-P", password]
else:
args += ["-E"] # Try trusted connection instead
args += ["-E"] # Try trusted connection instead
if db:
args += ["-d", db]
if defaults_file:
Expand Down
Loading