diff --git a/.github/workflows/lint_python.yml b/.github/workflows/lint_python.yml index d3c4844..b8e8a8d 100644 --- a/.github/workflows/lint_python.yml +++ b/.github/workflows/lint_python.yml @@ -6,8 +6,6 @@ jobs: steps: - name: Use Node.js uses: actions/setup-node@v2 - with: - node-version: '16' - uses: actions/checkout@v3 - uses: actions/setup-python@v3 - run: pip install --upgrade pip setuptools @@ -21,6 +19,6 @@ jobs: - run: pip install python-tds Sqlalchemy - run: mypy --ignore-missing-imports . || true - run: pytest || true - - run: pytest --doctest-modules || true - - run: shopt -s globstar && pyupgrade --py36-plus **/*.py || true - - run: safety check +# - run: pytest --doctest-modules || true +# - run: shopt -s globstar && pyupgrade --py36-plus **/*.py || true +# - run: safety check diff --git a/createdb.py b/createdb.py index efafb52..36e60f5 100755 --- a/createdb.py +++ b/createdb.py @@ -1,20 +1,28 @@ #!/usr/bin/env vpython3 import pytds + from t import username, userpass + class Main(object): def __init__(self): - self.conn = pytds.connect(dsn='127.0.0.1', user=username, password=userpass, database='master', autocommit=True) + self.conn = pytds.connect( + dsn="127.0.0.1", + user=username, + password=userpass, + database="master", + autocommit=True, + ) def close(self): self.conn.close() def main(self): sqls = ( - '''\ + """\ DROP DATABASE IF EXISTS testing; -''', - '''\ +""", + """\ CREATE DATABASE testing ON ( NAME = testing_dat, FILENAME = '/var/opt/mssql/data/testing_mdf.mdf', @@ -26,26 +34,29 @@ def main(self): SIZE = 5MB, FILEGROWTH = 5MB ) -''', - '''\ +""", + """\ USE testing -''', - '''\ +""", + """\ DROP SCHEMA IF EXISTS test_schema -''', - '''\ +""", + """\ CREATE SCHEMA test_schema; -''', +""", ) c = self.conn.cursor() for sql in sqls: c.execute(sql) c.close() + def main(): cls = Main() try: cls.main() finally: cls.close() + + main() diff --git a/linter.sh b/linter.sh new file mode 100755 index 0000000..e5ed7cd --- /dev/null +++ b/linter.sh @@ -0,0 +1,15 @@ +#!/bin/bash +xrun() { + echo "********************** $*" + vpy3 $* +} + +xrun bandit --recursive --skip B101 . # B101 is assert statements +xrun black --check . || true +xrun codespell --quiet-level=2 # --ignore-words-list="" --skip="" +xrun flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics +xrun isort --check-only --profile black . || true +xrun mypy --ignore-missing-imports . || true +#xrun pytest || true +#xrun pytest --doctest-modules || true +#xrun safety check diff --git a/setup.py b/setup.py index 873c7a9..92ab1f9 100644 --- a/setup.py +++ b/setup.py @@ -1,50 +1,54 @@ -import sys import os import re +import sys if sys.version_info < (2, 6): raise Exception("SQLAlchemy TDS requires Python 2.6 or higher.") from setuptools import setup -v = open(os.path.join(os.path.dirname(__file__), 'sqlalchemy_pytds', '__init__.py')) -VERSION = re.compile(r".*__version__ = '(.*?)'", re.S).match(v.read()).group(1) -v.close() +with open( + os.path.join(os.path.dirname(__file__), "sqlalchemy_pytds", "__init__.py") +) as fp: + rem = re.compile(r".*__version__ = \"(.*?)\"", re.S).match(fp.read()) + assert rem is not None + VERSION = rem.group(1) -readme = os.path.join(os.path.dirname(__file__), 'README.rst') +readme = os.path.join(os.path.dirname(__file__), "README.rst") requires = [ - 'python-tds', - 'SQLAlchemy >= 2.0', + "python-tds", + "SQLAlchemy >= 2.0", ] -setup(name='sqlalchemy_pytds', - version=VERSION, - description="A Microsoft SQL Server TDS connector for SQLAlchemy.", - long_description=open(readme).read(), - long_description_content_type='text/x-rst', - author='Grzegorz Makarewicz', - author_email='mak@trisoft.com.pl', - url='https://github.com/m32/sqlalchemy-tds', - license='MIT', - platforms=["any"], - packages=['sqlalchemy_pytds'], - classifiers=[ - 'Development Status :: 4 - Beta', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Topic :: Database :: Front-Ends', - ], - keywords='SQLAlchemy Microsoft SQL Server', - install_requires = requires, - include_package_data=True, - tests_require=['nose >= 0.11'], - test_suite="nose.collector", - entry_points={ - 'sqlalchemy.dialects': [ - 'mssql.pytds = sqlalchemy_pytds.dialect:MSDialect_pytds', - ] - } +setup( + name="sqlalchemy_pytds", + version=VERSION, + description="A Microsoft SQL Server TDS connector for SQLAlchemy.", + long_description=open(readme).read(), + long_description_content_type="text/x-rst", + author="Grzegorz Makarewicz", + author_email="mak@trisoft.com.pl", + url="https://github.com/m32/sqlalchemy-tds", + license="MIT", + platforms=["any"], + packages=["sqlalchemy_pytds"], + classifiers=[ + "Development Status :: 4 - Beta", + "Programming Language :: Python", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3.3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", + "Topic :: Database :: Front-Ends", + ], + keywords="SQLAlchemy Microsoft SQL Server", + install_requires=requires, + include_package_data=True, + tests_require=["nose >= 0.11"], + test_suite="nose.collector", + entry_points={ + "sqlalchemy.dialects": [ + "mssql.pytds = sqlalchemy_pytds.dialect:MSDialect_pytds", + ] + }, ) diff --git a/sqlalchemy_pytds/__init__.py b/sqlalchemy_pytds/__init__.py index f0d0d43..fcd4646 100644 --- a/sqlalchemy_pytds/__init__.py +++ b/sqlalchemy_pytds/__init__.py @@ -1,4 +1,4 @@ -__version__ = '1.0.1' +__version__ = "1.0.1" from sqlalchemy.dialects import registry diff --git a/sqlalchemy_pytds/connector.py b/sqlalchemy_pytds/connector.py index 36a228b..824de69 100644 --- a/sqlalchemy_pytds/connector.py +++ b/sqlalchemy_pytds/connector.py @@ -1,17 +1,17 @@ # connectors/pytds.py +import re + +import pytds import pytds.login +from pytds import tds_base, tds_session from sqlalchemy.connectors import Connector from sqlalchemy.util import asbool -import re -import pytds -from pytds import tds - prevexecute = pytds.Cursor.execute def execute(self, operation, params=None): - #print('execute:', operation, params) + # print('execute:', operation, params) if operation[:15] == "EXEC sp_columns": proc = "sp_columns" params = { @@ -35,21 +35,21 @@ def execute(self, operation, params=None): def process_tabname(self): r = self._reader total_length = r.get_smallint() - if not tds.tds_base.IS_TDS71_PLUS(self): + if not tds_base.IS_TDS71_PLUS(self): name_length = r.get_smallint() - tds.skipall(r, total_length) + tds_base.skipall(r, total_length) def process_colinfo(self): r = self._reader total_length = r.get_smallint() - tds.skipall(r, total_length) + tds_base.skipall(r, total_length) -tds._token_map.update( +tds_session._token_map.update( { - tds.tds_base.TDS_TABNAME_TOKEN: lambda self: process_tabname(self), - tds.tds_base.TDS_COLINFO_TOKEN: lambda self: process_colinfo(self), + tds_base.TDS_TABNAME_TOKEN: lambda self: process_tabname(self), + tds_base.TDS_COLINFO_TOKEN: lambda self: process_colinfo(self), } ) diff --git a/sqlalchemy_pytds/dialect.py b/sqlalchemy_pytds/dialect.py index 236acf8..ddc920e 100644 --- a/sqlalchemy_pytds/dialect.py +++ b/sqlalchemy_pytds/dialect.py @@ -1,8 +1,16 @@ -from sqlalchemy.dialects.mssql.base import MSDialect, MSSQLCompiler, MSIdentifierPreparer, MSExecutionContext from sqlalchemy import util +from sqlalchemy.dialects.mssql.base import ( + MSDialect, + MSExecutionContext, + MSIdentifierPreparer, + MSSQLCompiler, +) + from .connector import PyTDSConnector _server_side_id = util.counter() + + class SSCursor(object): def __init__(self, c): self._c = c @@ -11,21 +19,26 @@ def __init__(self, c): self._row = 0 def execute(self, statement, parameters): - _name = 'tc%08X' % _server_side_id() - sql = 'DECLARE ' + _name + ' CURSOR GLOBAL SCROLL STATIC READ_ONLY FOR ' + statement - #print(sql, parameters) + _name = "tc%08X" % _server_side_id() + sql = ( + "DECLARE " + + _name + + " CURSOR GLOBAL SCROLL STATIC READ_ONLY FOR " + + statement + ) + # print(sql, parameters) self._c.execute(sql, parameters) self._name = _name - sql = 'OPEN ' + _name + sql = "OPEN " + _name self._c.execute(sql) - self._c.execute('SELECT @@CURSOR_ROWS AS nrows') + self._c.execute("SELECT @@CURSOR_ROWS AS nrows") self._nrows = self._c.fetchone()[0] return self.fetchone() def close(self): - sql = 'CLOSE '+self._name + sql = "CLOSE " + self._name self._c.execute(sql) - sql = 'DEALLOCATE '+self._name + sql = "DEALLOCATE " + self._name self._c.execute(sql) self._c.close() self._name = None @@ -35,7 +48,7 @@ def close(self): def fetchone(self): if not (0 <= self._row < self._nrows): return None - sql = 'FETCH ABSOLUTE %d FROM %s' % (self._row+1, self._name) + sql = "FETCH ABSOLUTE %d FROM %s" % (self._row + 1, self._name) self._c.execute(sql) return self._c.fetchone() @@ -74,7 +87,7 @@ def goto(self, value): return False def __getattr__(self, name): - #print('getattr(%s)' %name) + # print('getattr(%s)' %name) return getattr(self._c, name) @@ -91,9 +104,11 @@ class MSExecutionContext_pytds(MSExecutionContext): def pre_exec(self): super(MSExecutionContext_pytds, self).pre_exec() - if self._select_lastrowid and \ - self.dialect.use_scope_identity and \ - len(self.parameters[0]): + if ( + self._select_lastrowid + and self.dialect.use_scope_identity + and len(self.parameters[0]) + ): self._embedded_scope_identity = True self.statement += "; select scope_identity()" @@ -113,7 +128,7 @@ def post_exec(self): super(MSExecutionContext_pytds, self).post_exec() def create_cursor(self): - usess = self.execution_options.get('stream_results', None) + usess = self.execution_options.get("stream_results", None) if usess: self._is_server_side = True return SSCursor(self._dbapi_connection.cursor()) @@ -126,7 +141,7 @@ class MSDialect_pytds(PyTDSConnector, MSDialect): execution_ctx_cls = MSExecutionContext_pytds statement_compiler = MSSQLCompiler_pytds - preparer = MSSQLIdentifierPreparer_pytds + preparer = MSSQLIdentifierPreparer_pytds # type: ignore supports_server_side_cursors = True supports_statement_cache = True @@ -141,9 +156,8 @@ def __init__(self, server_side_cursors=False, **params): self.server_side_cursors = server_side_cursors def set_isolation_level(self, connection, level): - if level == 'AUTOCOMMIT': + if level == "AUTOCOMMIT": connection.autocommit(True) else: connection.autocommit(False) - super(MSDialect_pytds, self).set_isolation_level(connection, - level) + super(MSDialect_pytds, self).set_isolation_level(connection, level) diff --git a/sqlalchemy_pytds/requirements.py b/sqlalchemy_pytds/requirements.py index 49d19a0..24385a4 100644 --- a/sqlalchemy_pytds/requirements.py +++ b/sqlalchemy_pytds/requirements.py @@ -1,6 +1,6 @@ +from sqlalchemy.testing import exclusions from sqlalchemy.testing.requirements import SuiteRequirements -from sqlalchemy.testing import exclusions class Requirements(SuiteRequirements): @@ -11,8 +11,8 @@ def temp_table_reflection(self): @property def order_by_col_from_union(self): """target database supports ordering by a column from a SELECT - inside of a UNION - E.g. (SELECT id, ...) UNION (SELECT id, ...) ORDER BY id """ + inside of a UNION + E.g. (SELECT id, ...) UNION (SELECT id, ...) ORDER BY id""" return exclusions.open() @property @@ -50,13 +50,13 @@ def returning(self): @property def empty_strings_varchar(self): """target database can persist/return an empty string with a - varchar. """ + varchar.""" return exclusions.open() @property def text_type(self): """Target database must support an unbounded Text() - type such as TEXT or CLOB """ + type such as TEXT or CLOB""" return exclusions.open() @property @@ -97,7 +97,7 @@ def unbounded_varchar(self): @property def implements_get_lastrowid(self): """target dialect implements the executioncontext.get_lastrowid() - method without reliance on RETURNING.""" + method without reliance on RETURNING.""" return exclusions.open() @property @@ -121,8 +121,7 @@ def dbapi_lastrowid(self): @property def schemas(self): - """Target database supports named schemas - """ + """Target database supports named schemas""" return exclusions.open() @property @@ -133,14 +132,12 @@ def views(self): @property def view_reflection(self): - """Target database supports view metadata - """ + """Target database supports view metadata""" return exclusions.open() @property def precision_numerics_enotation_large(self): - """Dialect converts small/large scale decimals into scientific notation - """ + """Dialect converts small/large scale decimals into scientific notation""" return exclusions.closed() @property diff --git a/t-ss.py b/t-ss.py index 5e2e7e5..3d4a105 100755 --- a/t-ss.py +++ b/t-ss.py @@ -1,12 +1,14 @@ #!/usr/bin/env vpython3 import sqlalchemy as sa from sqlalchemy.orm import sessionmaker + from t import username, userpass + class Main(object): def __init__(self): - self.engine = sa.create_engine('mssql+pytds://'+username+':'+userpass+'@127.0.0.1/testing') - self.metadata = sa.MetaData(self.engine) + self.engine = sa.create_engine('mssql+pytds://'+username+':'+userpass.replace('@', '%40')+'@127.0.0.1/testing') + self.metadata = sa.MetaData() def close(self): pass diff --git a/t.py b/t.py index f8c5125..645db5e 100644 --- a/t.py +++ b/t.py @@ -2,10 +2,10 @@ logging.basicConfig() logging.getLogger().setLevel(logging.DEBUG) -logging.getLogger('sqlalchemy').setLevel(logging.DEBUG) -#logging.getLogger('pytds').setLevel(logging.DEBUG) -#logging.getLogger('pytds.__init__').setLevel(logging.DEBUG) -#logging.getLogger('pytds.tds').setLevel(logging.DEBUG) +logging.getLogger("sqlalchemy").setLevel(logging.DEBUG) +# logging.getLogger('pytds').setLevel(logging.DEBUG) +# logging.getLogger('pytds.__init__').setLevel(logging.DEBUG) +# logging.getLogger('pytds.tds').setLevel(logging.DEBUG) -username = 'sa' -userpass = 'SaAdmin1@' +username = "sa" +userpass = "SaAdmin1@" diff --git a/test/conftest.py b/test/conftest.py index 93cf3a1..733916f 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -1,5 +1,5 @@ -from sqlalchemy.dialects import registry import pytest +from sqlalchemy.dialects import registry registry.register("mssql.pytds", "sqlalchemy_pytds.dialect", "MSDialect_pytds")