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.
@@ -0,0 +1,5 @@
1
+ __version__ = "1.0.3"
2
+
3
+ from sqlalchemy.dialects import registry
4
+
5
+ registry.register("mssql.pytds", "sqlalchemy_pytds.dialect", "MSDialect_pytds")
@@ -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,9 @@
1
+ from sqlalchemy.testing.provision import normalize_sequence
2
+
3
+
4
+ @normalize_sequence.for_db("mssql")
5
+ def normalize_sequence(cfg, sequence):
6
+ # avoid numeric overflow (int) in tests
7
+ if sequence.start is None:
8
+ sequence.start = 1
9
+ return sequence
@@ -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,6 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py2-none-any
5
+ Tag: py3-none-any
6
+
@@ -0,0 +1,2 @@
1
+ [sqlalchemy.dialects]
2
+ mssql.pytds = sqlalchemy_pytds.dialect:MSDialect_pytds
@@ -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