sqlalchemy-pytds 1.0.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sqlalchemy_pytds/__init__.py +5 -0
- sqlalchemy_pytds/connector.py +141 -0
- sqlalchemy_pytds/dialect.py +212 -0
- sqlalchemy_pytds/provision.py +9 -0
- sqlalchemy_pytds/requirements.py +209 -0
- sqlalchemy_pytds-1.0.3.dist-info/METADATA +22 -0
- sqlalchemy_pytds-1.0.3.dist-info/RECORD +11 -0
- sqlalchemy_pytds-1.0.3.dist-info/WHEEL +6 -0
- sqlalchemy_pytds-1.0.3.dist-info/entry_points.txt +2 -0
- sqlalchemy_pytds-1.0.3.dist-info/licenses/LICENSE +21 -0
- sqlalchemy_pytds-1.0.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# connectors/pytds.py
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
from sqlalchemy.connectors import Connector
|
|
5
|
+
from sqlalchemy.util import asbool
|
|
6
|
+
|
|
7
|
+
import pytds
|
|
8
|
+
import pytds.login
|
|
9
|
+
from pytds import tds_base, tds_session
|
|
10
|
+
|
|
11
|
+
prevexecute = pytds.Cursor.execute
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def execute(self, operation, params=None):
|
|
15
|
+
# print('execute:', operation, params)
|
|
16
|
+
if operation[:15] == "EXEC sp_columns":
|
|
17
|
+
proc = "sp_columns"
|
|
18
|
+
params = {
|
|
19
|
+
"@table_name": params["table_name"],
|
|
20
|
+
"@table_owner": params["table_owner"],
|
|
21
|
+
}
|
|
22
|
+
return pytds.Cursor.callproc(self, proc, params)
|
|
23
|
+
elif operation[:10] == "sp_columns":
|
|
24
|
+
proc = "sp_columns"
|
|
25
|
+
params = {
|
|
26
|
+
"@table_name": params["table_name"],
|
|
27
|
+
"@table_owner": params["table_owner"],
|
|
28
|
+
}
|
|
29
|
+
return pytds.Cursor.callproc(self, proc, params)
|
|
30
|
+
return prevexecute(self, operation, params)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
pytds.Cursor.execute = execute
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def process_tabname(self):
|
|
37
|
+
r = self._reader
|
|
38
|
+
total_length = r.get_smallint()
|
|
39
|
+
if not tds_base.IS_TDS71_PLUS(self):
|
|
40
|
+
name_length = r.get_smallint()
|
|
41
|
+
tds_base.skipall(r, total_length)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def process_colinfo(self):
|
|
45
|
+
r = self._reader
|
|
46
|
+
total_length = r.get_smallint()
|
|
47
|
+
tds_base.skipall(r, total_length)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
tds_session._token_map.update(
|
|
51
|
+
{
|
|
52
|
+
tds_base.TDS_TABNAME_TOKEN: lambda self: process_tabname(self),
|
|
53
|
+
tds_base.TDS_COLINFO_TOKEN: lambda self: process_colinfo(self),
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class PyTDSConnector(Connector):
|
|
59
|
+
driver = "pytds"
|
|
60
|
+
|
|
61
|
+
supports_sane_multi_rowcount = False
|
|
62
|
+
supports_unicode = True
|
|
63
|
+
supports_unicode_binds = True
|
|
64
|
+
supports_unicode_statements = True
|
|
65
|
+
supports_native_decimal = True
|
|
66
|
+
default_paramstyle = "pyformat"
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def import_dbapi(cls):
|
|
70
|
+
return pytds
|
|
71
|
+
|
|
72
|
+
def is_disconnect(self, e, connection, cursor):
|
|
73
|
+
if isinstance(e, self.dbapi.ProgrammingError):
|
|
74
|
+
return "The cursor's connection has been closed." in str(
|
|
75
|
+
e
|
|
76
|
+
) or "Attempt to use a closed connection." in str(e)
|
|
77
|
+
elif isinstance(e, self.dbapi.Error):
|
|
78
|
+
return "[08S01]" in str(e)
|
|
79
|
+
else:
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
def create_connect_args(self, url):
|
|
83
|
+
opts = url.translate_connect_args(username="user")
|
|
84
|
+
opts.update(url.query)
|
|
85
|
+
|
|
86
|
+
keys = opts
|
|
87
|
+
query = url.query
|
|
88
|
+
|
|
89
|
+
connect_args = {}
|
|
90
|
+
for param in ("autocommit", "use_mars", "as_dict"):
|
|
91
|
+
if param in keys:
|
|
92
|
+
connect_args[param] = asbool(keys.pop(param))
|
|
93
|
+
for param in ("port", "timeout", "login_timeout"):
|
|
94
|
+
if param in keys:
|
|
95
|
+
connect_args[param] = int(keys.pop(param))
|
|
96
|
+
for param in ("host", "user", "password", "database", "auth_method"):
|
|
97
|
+
if param in keys:
|
|
98
|
+
connect_args[param] = keys.pop(param)
|
|
99
|
+
|
|
100
|
+
connect_args["dsn"] = connect_args.pop("host", "localhost")
|
|
101
|
+
|
|
102
|
+
if "auth_method" in connect_args:
|
|
103
|
+
if connect_args["auth_method"] == "mssql":
|
|
104
|
+
del connect_args["auth_method"]
|
|
105
|
+
elif connect_args["auth_method"] == "ntlm":
|
|
106
|
+
connect_args["auth"] = pytds.login.NtlmAuth(
|
|
107
|
+
connect_args["user"], connect_args["password"]
|
|
108
|
+
)
|
|
109
|
+
del connect_args["auth_method"]
|
|
110
|
+
del connect_args["user"]
|
|
111
|
+
del connect_args["password"]
|
|
112
|
+
elif connect_args["auth_method"] == "sso":
|
|
113
|
+
connect_args["use_sso"] = True
|
|
114
|
+
del connect_args["auth_method"]
|
|
115
|
+
else:
|
|
116
|
+
raise Exception("Unknown auth_method " + connect_args["auth_method"])
|
|
117
|
+
|
|
118
|
+
return [[], connect_args]
|
|
119
|
+
|
|
120
|
+
def _dbapi_version(self):
|
|
121
|
+
if not self.dbapi:
|
|
122
|
+
return ()
|
|
123
|
+
return self._parse_dbapi_version(self.dbapi.version)
|
|
124
|
+
|
|
125
|
+
def _parse_dbapi_version(self, vers):
|
|
126
|
+
m = re.match(r"(?:py.*-)?([\d\.]+)(?:-(\w+))?", vers)
|
|
127
|
+
if not m:
|
|
128
|
+
return ()
|
|
129
|
+
vers = tuple([int(x) for x in m.group(1).split(".")])
|
|
130
|
+
if m.group(2):
|
|
131
|
+
vers += (m.group(2),)
|
|
132
|
+
return vers
|
|
133
|
+
|
|
134
|
+
def _get_server_version_info(self, connection):
|
|
135
|
+
l = connection.connection.product_version
|
|
136
|
+
version = []
|
|
137
|
+
for i in range(4):
|
|
138
|
+
version.append(l & 0xFF)
|
|
139
|
+
l >>= 8
|
|
140
|
+
version.reverse()
|
|
141
|
+
return tuple(version)
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
from sqlalchemy import util
|
|
4
|
+
from sqlalchemy.dialects.mssql import SQL_VARIANT
|
|
5
|
+
from sqlalchemy.dialects.mssql.base import (
|
|
6
|
+
MSDialect,
|
|
7
|
+
MSExecutionContext,
|
|
8
|
+
MSIdentifierPreparer,
|
|
9
|
+
MSSQLCompiler,
|
|
10
|
+
)
|
|
11
|
+
from sqlalchemy.engine.interfaces import ExecuteStyle
|
|
12
|
+
|
|
13
|
+
from pytds import tds_base, tds_types
|
|
14
|
+
|
|
15
|
+
from .connector import PyTDSConnector
|
|
16
|
+
|
|
17
|
+
_server_side_id = util.counter()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SSCursor(object):
|
|
21
|
+
def __init__(self, c):
|
|
22
|
+
self._c = c
|
|
23
|
+
self._name = None
|
|
24
|
+
self._nrows = 0
|
|
25
|
+
self._row = 0
|
|
26
|
+
|
|
27
|
+
def execute(self, statement, parameters):
|
|
28
|
+
_name = "tc%08X" % _server_side_id()
|
|
29
|
+
sql = (
|
|
30
|
+
"DECLARE "
|
|
31
|
+
+ _name
|
|
32
|
+
+ " CURSOR GLOBAL SCROLL STATIC READ_ONLY FOR "
|
|
33
|
+
+ statement
|
|
34
|
+
)
|
|
35
|
+
# print(sql, parameters)
|
|
36
|
+
self._c.execute(sql, parameters)
|
|
37
|
+
self._name = _name
|
|
38
|
+
sql = "OPEN " + _name
|
|
39
|
+
self._c.execute(sql)
|
|
40
|
+
self._c.execute("SELECT @@CURSOR_ROWS AS nrows")
|
|
41
|
+
self._nrows = self._c.fetchone()[0]
|
|
42
|
+
return self.fetchone()
|
|
43
|
+
|
|
44
|
+
def close(self):
|
|
45
|
+
sql = "CLOSE " + self._name
|
|
46
|
+
self._c.execute(sql)
|
|
47
|
+
sql = "DEALLOCATE " + self._name
|
|
48
|
+
self._c.execute(sql)
|
|
49
|
+
self._c.close()
|
|
50
|
+
self._name = None
|
|
51
|
+
self._nrows = 0
|
|
52
|
+
self._row = 0
|
|
53
|
+
|
|
54
|
+
def fetchone(self):
|
|
55
|
+
if not (0 <= self._row < self._nrows):
|
|
56
|
+
return None
|
|
57
|
+
sql = "FETCH ABSOLUTE %d FROM %s" % (self._row + 1, self._name)
|
|
58
|
+
self._c.execute(sql)
|
|
59
|
+
return self._c.fetchone()
|
|
60
|
+
|
|
61
|
+
def movefirst(self):
|
|
62
|
+
value = 0
|
|
63
|
+
if value >= 0 and value < self._nrows:
|
|
64
|
+
self._row = value
|
|
65
|
+
return True
|
|
66
|
+
return False
|
|
67
|
+
|
|
68
|
+
def moveprev(self):
|
|
69
|
+
value = self._row - 1
|
|
70
|
+
if value >= 0 and value < self._nrows:
|
|
71
|
+
self._row = value
|
|
72
|
+
return True
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
def movenext(self):
|
|
76
|
+
value = self._row + 1
|
|
77
|
+
if value >= 0 and value < self._nrows:
|
|
78
|
+
self._row = value
|
|
79
|
+
return True
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
def movelast(self):
|
|
83
|
+
value = self._nrows - 1
|
|
84
|
+
if value >= 0 and value < self._nrows:
|
|
85
|
+
self._row = value
|
|
86
|
+
return True
|
|
87
|
+
return False
|
|
88
|
+
|
|
89
|
+
def goto(self, value):
|
|
90
|
+
if value >= 0 and value < self._nrows:
|
|
91
|
+
self._row = value
|
|
92
|
+
return True
|
|
93
|
+
return False
|
|
94
|
+
|
|
95
|
+
def __getattr__(self, name):
|
|
96
|
+
# print('getattr(%s)' %name)
|
|
97
|
+
return getattr(self._c, name)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class MSSQLCompiler_pytds(MSSQLCompiler):
|
|
101
|
+
pass
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class MSSQLIdentifierPreparer_pytds(MSIdentifierPreparer):
|
|
105
|
+
|
|
106
|
+
def _escape_identifier(self, value: str) -> str:
|
|
107
|
+
value = value.replace("]", "]]")
|
|
108
|
+
value = value.replace("%", "%%")
|
|
109
|
+
return value
|
|
110
|
+
|
|
111
|
+
def _unescape_identifier(self, value: str) -> str:
|
|
112
|
+
return value.replace("]]", "]")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class MSExecutionContext_pytds(MSExecutionContext):
|
|
116
|
+
_embedded_scope_identity = False
|
|
117
|
+
|
|
118
|
+
def pre_exec(self):
|
|
119
|
+
super(MSExecutionContext_pytds, self).pre_exec()
|
|
120
|
+
if (
|
|
121
|
+
self._select_lastrowid
|
|
122
|
+
and self.dialect.use_scope_identity
|
|
123
|
+
and len(self.parameters[0])
|
|
124
|
+
):
|
|
125
|
+
self._embedded_scope_identity = True
|
|
126
|
+
|
|
127
|
+
self.statement += "; select scope_identity()"
|
|
128
|
+
|
|
129
|
+
def post_exec(self):
|
|
130
|
+
if self._embedded_scope_identity:
|
|
131
|
+
while True:
|
|
132
|
+
try:
|
|
133
|
+
row = self.cursor.fetchall()[0]
|
|
134
|
+
break
|
|
135
|
+
except self.dialect.dbapi.Error as e:
|
|
136
|
+
self.cursor.nextset()
|
|
137
|
+
|
|
138
|
+
self._lastrowid = int(row[0])
|
|
139
|
+
self.cursor.lastrowid = int(row[0])
|
|
140
|
+
self._select_lastrowid = False
|
|
141
|
+
super(MSExecutionContext_pytds, self).post_exec()
|
|
142
|
+
|
|
143
|
+
def create_cursor(self):
|
|
144
|
+
usess = self.execution_options.get("stream_results", None)
|
|
145
|
+
if usess:
|
|
146
|
+
self._is_server_side = True
|
|
147
|
+
return SSCursor(self._dbapi_connection.cursor())
|
|
148
|
+
else:
|
|
149
|
+
self._is_server_side = False
|
|
150
|
+
return self._dbapi_connection.cursor()
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class MSDialect_pytds(PyTDSConnector, MSDialect):
|
|
154
|
+
|
|
155
|
+
execution_ctx_cls = MSExecutionContext_pytds
|
|
156
|
+
statement_compiler = MSSQLCompiler_pytds
|
|
157
|
+
preparer = MSSQLIdentifierPreparer_pytds # type: ignore
|
|
158
|
+
|
|
159
|
+
supports_server_side_cursors = True
|
|
160
|
+
supports_statement_cache = True
|
|
161
|
+
supports_sane_rowcount_returning = True
|
|
162
|
+
supports_sane_multi_rowcount = False
|
|
163
|
+
supports_native_decimal = True
|
|
164
|
+
supports_native_uuid = True
|
|
165
|
+
|
|
166
|
+
def __init__(self, server_side_cursors=False, **params):
|
|
167
|
+
super(MSDialect_pytds, self).__init__(**params)
|
|
168
|
+
self.use_scope_identity = True
|
|
169
|
+
self.server_side_cursors = server_side_cursors
|
|
170
|
+
|
|
171
|
+
def do_execute(self, cursor, statement, parameters, context=None):
|
|
172
|
+
if context and context.isinsert or context.isupdate:
|
|
173
|
+
tbl = context.compiled.compile_state.dml_table
|
|
174
|
+
# print('*'*20, 'do_execute')
|
|
175
|
+
# print('stmt:', statement)
|
|
176
|
+
# print('parm:', parameters)
|
|
177
|
+
for c in tbl._columns:
|
|
178
|
+
if isinstance(c.type, SQL_VARIANT):
|
|
179
|
+
todo = [
|
|
180
|
+
name
|
|
181
|
+
for name in parameters.keys()
|
|
182
|
+
if c.name == name or re.match(c.name + r"__(\d+)", name)
|
|
183
|
+
]
|
|
184
|
+
# print('todo:', todo)
|
|
185
|
+
for name in todo:
|
|
186
|
+
v = parameters.get(name, None)
|
|
187
|
+
# print('cvt', name, v)
|
|
188
|
+
if isinstance(v, str):
|
|
189
|
+
# assert len(v) <= 4000
|
|
190
|
+
parameters[name] = tds_base.Param(
|
|
191
|
+
name=name,
|
|
192
|
+
type=tds_types.NVarCharType(size=len(v)),
|
|
193
|
+
value=v,
|
|
194
|
+
flags=0,
|
|
195
|
+
)
|
|
196
|
+
elif isinstance(v, bytes):
|
|
197
|
+
# assert len(v) <= 8000
|
|
198
|
+
parameters[name] = tds_base.Param(
|
|
199
|
+
name=name,
|
|
200
|
+
type=tds_types.VarBinaryType(size=len(v)),
|
|
201
|
+
value=v,
|
|
202
|
+
flags=0,
|
|
203
|
+
)
|
|
204
|
+
# print('parm:', parameters)
|
|
205
|
+
cursor.execute(statement, parameters)
|
|
206
|
+
|
|
207
|
+
def set_isolation_level(self, connection, level):
|
|
208
|
+
if level == "AUTOCOMMIT":
|
|
209
|
+
connection.autocommit(True)
|
|
210
|
+
else:
|
|
211
|
+
connection.autocommit(False)
|
|
212
|
+
super(MSDialect_pytds, self).set_isolation_level(connection, level)
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
from sqlalchemy.testing import exclusions
|
|
2
|
+
from sqlalchemy.testing.requirements import SuiteRequirements
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Requirements(SuiteRequirements):
|
|
6
|
+
|
|
7
|
+
@property
|
|
8
|
+
def temp_table_reflection(self):
|
|
9
|
+
return exclusions.closed()
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def order_by_col_from_union(self):
|
|
13
|
+
"""target database supports ordering by a column from a SELECT
|
|
14
|
+
inside of a UNION
|
|
15
|
+
E.g. (SELECT id, ...) UNION (SELECT id, ...) ORDER BY id"""
|
|
16
|
+
return exclusions.open()
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def independent_connections(self):
|
|
20
|
+
return exclusions.open()
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def implicitly_named_constraints(self):
|
|
24
|
+
"""target database must apply names to unnamed constraints."""
|
|
25
|
+
return exclusions.open()
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def reflects_pk_names(self):
|
|
29
|
+
return exclusions.open()
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def index_reflection(self):
|
|
33
|
+
return exclusions.closed()
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def unique_constraint_reflection(self):
|
|
37
|
+
"""target dialect supports reflection of unique constraints"""
|
|
38
|
+
return exclusions.closed()
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def self_referential_foreign_keys(self):
|
|
42
|
+
"""Target database must support self-referential foreign keys."""
|
|
43
|
+
return exclusions.open()
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def returning(self):
|
|
47
|
+
"""target platform supports RETURNING."""
|
|
48
|
+
return exclusions.closed()
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def empty_strings_varchar(self):
|
|
52
|
+
"""target database can persist/return an empty string with a
|
|
53
|
+
varchar."""
|
|
54
|
+
return exclusions.open()
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def text_type(self):
|
|
58
|
+
"""Target database must support an unbounded Text()
|
|
59
|
+
type such as TEXT or CLOB"""
|
|
60
|
+
return exclusions.open()
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def datetime_microseconds(self):
|
|
64
|
+
"""target dialect supports representation of Python
|
|
65
|
+
datetime.datetime() with microsecond objects."""
|
|
66
|
+
return exclusions.closed()
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def datetime_historic(self):
|
|
70
|
+
"""target dialect supports representation of Python
|
|
71
|
+
datetime.datetime() objects with historic (pre 1970) values."""
|
|
72
|
+
return exclusions.open()
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def date_historic(self):
|
|
76
|
+
"""target dialect supports representation of Python
|
|
77
|
+
datetime.date() objects with historic (pre 1970) values."""
|
|
78
|
+
return exclusions.open()
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def time(self):
|
|
82
|
+
"""target dialect supports representation of Python
|
|
83
|
+
datetime.time() objects."""
|
|
84
|
+
return exclusions.open()
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def time_microseconds(self):
|
|
88
|
+
"""target dialect supports representation of Python
|
|
89
|
+
datetime.time() with microsecond objects."""
|
|
90
|
+
return exclusions.open()
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def unbounded_varchar(self):
|
|
94
|
+
"""Target database must support VARCHAR with no length"""
|
|
95
|
+
return exclusions.open()
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def implements_get_lastrowid(self):
|
|
99
|
+
"""target dialect implements the executioncontext.get_lastrowid()
|
|
100
|
+
method without reliance on RETURNING."""
|
|
101
|
+
return exclusions.open()
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def emulated_lastrowid(self):
|
|
105
|
+
"""target dialect retrieves cursor.lastrowid, or fetches
|
|
106
|
+
from a database-side function after an insert() construct executes,
|
|
107
|
+
within the get_lastrowid() method.
|
|
108
|
+
|
|
109
|
+
Only dialects that "pre-execute", or need RETURNING to get last
|
|
110
|
+
inserted id, would return closed/fail/skip for this.
|
|
111
|
+
"""
|
|
112
|
+
return exclusions.open()
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def dbapi_lastrowid(self):
|
|
116
|
+
"""target platform includes a 'lastrowid' accessor on the DBAPI
|
|
117
|
+
cursor object.
|
|
118
|
+
|
|
119
|
+
"""
|
|
120
|
+
return exclusions.open()
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def schemas(self):
|
|
124
|
+
"""Target database supports named schemas"""
|
|
125
|
+
return exclusions.open()
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def views(self):
|
|
129
|
+
"""Target database must support VIEWs."""
|
|
130
|
+
|
|
131
|
+
return exclusions.open()
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def view_reflection(self):
|
|
135
|
+
"""Target database supports view metadata"""
|
|
136
|
+
return exclusions.open()
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def precision_numerics_enotation_large(self):
|
|
140
|
+
"""Dialect converts small/large scale decimals into scientific notation"""
|
|
141
|
+
return exclusions.closed()
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def temporary_tables(self):
|
|
145
|
+
"""target database supports temporary tables"""
|
|
146
|
+
return exclusions.open()
|
|
147
|
+
|
|
148
|
+
@property
|
|
149
|
+
def temp_table_names(self):
|
|
150
|
+
"""target dialect supports listing of temporary table names"""
|
|
151
|
+
return exclusions.closed()
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def offset(self):
|
|
155
|
+
"""target database can render OFFSET, or an equivalent, in a
|
|
156
|
+
SELECT.
|
|
157
|
+
"""
|
|
158
|
+
return exclusions.open()
|
|
159
|
+
|
|
160
|
+
@property
|
|
161
|
+
def bound_limit_offset(self):
|
|
162
|
+
"""target database can render LIMIT and/or OFFSET using a bound
|
|
163
|
+
parameter
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
return exclusions.open()
|
|
167
|
+
|
|
168
|
+
@property
|
|
169
|
+
def duplicate_key_raises_integrity_error(self):
|
|
170
|
+
return exclusions.open()
|
|
171
|
+
|
|
172
|
+
@property
|
|
173
|
+
def parens_in_union_contained_select_w_limit_offset(self):
|
|
174
|
+
return exclusions.closed()
|
|
175
|
+
|
|
176
|
+
@property
|
|
177
|
+
def parens_in_union_contained_select_wo_limit_offset(self):
|
|
178
|
+
return exclusions.open()
|
|
179
|
+
|
|
180
|
+
@property
|
|
181
|
+
def broken_cx_oracle6_numerics(self):
|
|
182
|
+
return exclusions.closed()
|
|
183
|
+
|
|
184
|
+
@property
|
|
185
|
+
def floats_to_four_decimals(self):
|
|
186
|
+
return exclusions.closed()
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def server_side_cursors(self):
|
|
190
|
+
"""Target dialect must support server side cursors."""
|
|
191
|
+
return exclusions.closed()
|
|
192
|
+
|
|
193
|
+
@property
|
|
194
|
+
def array_type(self):
|
|
195
|
+
# ARRAY type not implemented.
|
|
196
|
+
return exclusions.closed()
|
|
197
|
+
|
|
198
|
+
@property
|
|
199
|
+
def sane_multi_rowcount(self):
|
|
200
|
+
return exclusions.closed()
|
|
201
|
+
|
|
202
|
+
@property
|
|
203
|
+
def unicode_data_no_special_types(self):
|
|
204
|
+
"""Target database/dialect can receive / deliver / compare data with
|
|
205
|
+
non-ASCII characters in plain VARCHAR, TEXT columns, without the need
|
|
206
|
+
for special "national" datatypes like NVARCHAR or similar.
|
|
207
|
+
|
|
208
|
+
"""
|
|
209
|
+
return exclusions.closed()
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sqlalchemy_pytds
|
|
3
|
+
Version: 1.0.3
|
|
4
|
+
Summary: A Microsoft SQL Server TDS connector for SQLAlchemy.
|
|
5
|
+
Author-email: Grzegorz Makarewicz <mak@trisoft.com.pl>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/m32/sqlalchemy-tds
|
|
8
|
+
Keywords: SQLAlchemy,Microsoft SQL Server
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Programming Language :: Python
|
|
11
|
+
Classifier: Programming Language :: Python :: 2.7
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.4
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.5
|
|
15
|
+
Classifier: Topic :: Database :: Front-Ends
|
|
16
|
+
Description-Content-Type: text/x-rst
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: python-tds
|
|
19
|
+
Requires-Dist: SQLAlchemy>=2.0
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
A Microsoft SQL Server TDS connector for SQLAlchemy.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
sqlalchemy_pytds/__init__.py,sha256=4wwiQa5tSK1D8jFfdWvySDwJSMP1NBbwb5yPPaAQ_60,145
|
|
2
|
+
sqlalchemy_pytds/connector.py,sha256=qOBEewXYSMBMg1LqZTS7mZf536OAvMMlt_uzF1kSWls,4339
|
|
3
|
+
sqlalchemy_pytds/dialect.py,sha256=uk-3mjO8q0M0sDiD-9IGLXCh9K8QvoxbnCeTM9yXeOQ,6694
|
|
4
|
+
sqlalchemy_pytds/provision.py,sha256=PaQpbjV2OAt00x0XP3rTSkDPsN2p-XNYEFmUkPCD8X0,259
|
|
5
|
+
sqlalchemy_pytds/requirements.py,sha256=xKJ3IrhdNeZ_OunUIznJw1c5sKwtte-rf5c-_K9K8gY,6019
|
|
6
|
+
sqlalchemy_pytds-1.0.3.dist-info/licenses/LICENSE,sha256=1q5zV6eF0hfUtq1TFjccuSDABoec7G3TrOd6eJFIV-Q,1086
|
|
7
|
+
sqlalchemy_pytds-1.0.3.dist-info/METADATA,sha256=EFuftnaKs1F2Z_digB52TRHEDCM9Y99CdUJY9IXOI10,815
|
|
8
|
+
sqlalchemy_pytds-1.0.3.dist-info/WHEEL,sha256=Uvlg2aNlEKdilsyFSJt1QX5me0eEbCjQzR3nZeFkvy4,109
|
|
9
|
+
sqlalchemy_pytds-1.0.3.dist-info/entry_points.txt,sha256=Jbzf2UfqTNoIQN2oJ2gHXAuhcTsNLn020Q8LY23PMyI,77
|
|
10
|
+
sqlalchemy_pytds-1.0.3.dist-info/top_level.txt,sha256=vlDsdgZP5RBe_PCE3iyvjUl_h9asG4Wy-HWw6db4_6Q,17
|
|
11
|
+
sqlalchemy_pytds-1.0.3.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2016 Grzegorz Makarewicz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
sqlalchemy_pytds
|