Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-pyodbc for openSUSE:Factory 
checked in at 2026-04-18 21:35:15
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-pyodbc (Old)
 and      /work/SRC/openSUSE:Factory/.python-pyodbc.new.11940 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-pyodbc"

Sat Apr 18 21:35:15 2026 rev:16 rq:1347828 version:5.3.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-pyodbc/python-pyodbc.changes      
2025-06-11 16:27:44.747526495 +0200
+++ /work/SRC/openSUSE:Factory/.python-pyodbc.new.11940/python-pyodbc.changes   
2026-04-18 21:35:24.847213969 +0200
@@ -1,0 +2,11 @@
+Thu Apr 16 12:52:55 UTC 2026 - John Paul Adrian Glaubitz 
<[email protected]>
+
+- Update to 5.3.0
+  * Add support for Python 3.14 including wheels, all on PyPI now,
+    by @keitherskine
+  * Drop support for EOL Python 3.8 by @MatthijsKok in (#1445)
+  * Port SQLite tests to pytest by @shramov in (#1440)
+  * Use HOMEBREW_PREFIX to locate Homebrew by @RA80533 in (#1212)
+  * Do not perform type check on NULL pointer by @shramov in (#1439)
+
+-------------------------------------------------------------------

Old:
----
  pyodbc-5.2.0.tar.gz

New:
----
  pyodbc-5.3.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-pyodbc.spec ++++++
--- /var/tmp/diff_new_pack.QPaR1i/_old  2026-04-18 21:35:25.523241520 +0200
+++ /var/tmp/diff_new_pack.QPaR1i/_new  2026-04-18 21:35:25.527241683 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package python-pyodbc
 #
-# Copyright (c) 2025 SUSE LLC
+# Copyright (c) 2026 SUSE LLC and contributors
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -18,7 +18,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-pyodbc
-Version:        5.2.0
+Version:        5.3.0
 Release:        0
 Summary:        Python ODBC API
 License:        MIT

++++++ pyodbc-5.2.0.tar.gz -> pyodbc-5.3.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyodbc-5.2.0/PKG-INFO new/pyodbc-5.3.0/PKG-INFO
--- old/pyodbc-5.2.0/PKG-INFO   2024-10-16 03:23:40.199830300 +0200
+++ new/pyodbc-5.3.0/PKG-INFO   2025-10-17 19:30:22.705030400 +0200
@@ -1,25 +1,58 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.4
 Name: pyodbc
-Version: 5.2.0
-Summary: DB API Module for ODBC
+Version: 5.3.0
+Summary: DB API module for ODBC
 Home-page: https://github.com/mkleehammer/pyodbc
+Author-email: Michael Kleehammer <[email protected]>
 Maintainer: Michael Kleehammer
-Maintainer-email: [email protected]
-License: MIT-0
-Platform: UNKNOWN
+Maintainer-email: Michael Kleehammer <[email protected]>
+License: MIT
+Project-URL: Homepage, https://github.com/mkleehammer/pyodbc
 Classifier: Development Status :: 5 - Production/Stable
 Classifier: Intended Audience :: Developers
 Classifier: Intended Audience :: System Administrators
-Classifier: License :: OSI Approved :: MIT No Attribution License (MIT-0)
 Classifier: Operating System :: Microsoft :: Windows
 Classifier: Operating System :: POSIX
 Classifier: Programming Language :: Python
 Classifier: Programming Language :: Python :: 3
 Classifier: Topic :: Database
-Requires-Python: >=3.8
+Requires-Python: >=3.9
+Description-Content-Type: text/markdown
 License-File: LICENSE.txt
+Dynamic: home-page
+Dynamic: license-file
+Dynamic: maintainer
+Dynamic: requires-python
 
-pyodbc is an open source Python module that makes accessing ODBC databases 
simple.
-It implements the [DB API 2.0](https://www.python.org/dev/peps/pep-0249)
-specification but is packed with even more Pythonic convenience.
+# pyodbc
 
+[![Windows 
build](https://ci.appveyor.com/api/projects/status/github/mkleehammer/pyodbc?branch=master&svg=true&passingText=Windows%20build&failingText=Windows%20build)](https://ci.appveyor.com/project/mkleehammer/pyodbc)
+[![Ubuntu 
build](https://github.com/mkleehammer/pyodbc/actions/workflows/ubuntu_build.yml/badge.svg)](https://github.com/mkleehammer/pyodbc/actions/workflows/ubuntu_build.yml)
+[![PyPI](https://img.shields.io/pypi/v/pyodbc?color=brightgreen)](https://pypi.org/project/pyodbc/)
+
+pyodbc is an open source Python module that makes accessing ODBC databases 
simple.  It
+implements the [DB API 2.0](https://www.python.org/dev/peps/pep-0249) 
specification but is packed with even more Pythonic convenience.
+
+The easiest way to install pyodbc is to use pip:
+
+    python -m pip install pyodbc
+
+On Macs, you should probably install unixODBC first if you don't already have 
an ODBC
+driver manager installed.  For example, using the [homebrew](https://brew.sh/) 
package manager:
+
+    brew install unixodbc
+    python -m pip install pyodbc
+
+Similarly, on Unix you should make sure you have an ODBC driver manager 
installed before
+installing pyodbc.  See the 
[docs](https://github.com/mkleehammer/pyodbc/wiki/Install)
+for more information about how to do this on different Unix flavors.  (On 
Windows, the
+ODBC driver manager is built-in.)
+
+Precompiled binary wheels are provided for multiple Python versions on most 
Windows, macOS,
+and Linux platforms.  On other platforms pyodbc will be built from the source 
code.  Note,
+pyodbc contains C++ extensions so you will need a suitable C++ compiler when 
building from
+source.  See the [docs](https://github.com/mkleehammer/pyodbc/wiki/Install) 
for details.
+
+[Documentation](https://github.com/mkleehammer/pyodbc/wiki)
+
+[Release Notes](https://github.com/mkleehammer/pyodbc/releases)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyodbc-5.2.0/pyproject.toml 
new/pyodbc-5.3.0/pyproject.toml
--- old/pyodbc-5.2.0/pyproject.toml     2024-10-16 03:23:37.000000000 +0200
+++ new/pyodbc-5.3.0/pyproject.toml     2025-10-17 19:30:17.000000000 +0200
@@ -1,8 +1,8 @@
 [project]
 name = "pyodbc"
-version = "5.2.0"
+version = "5.3.0"
 
-requires-python = ">=3.8"
+requires-python = ">=3.9"
 # This is used by the GitHub action that builds release artifacts using 
cibuildwheel.
 # cibuildwheel reads this directly:
 #
@@ -10,7 +10,7 @@
 
 description = "DB API module for ODBC"
 readme = "README.md"
-license = {text = "MIT-0 License"}
+license = { text = "MIT" }
 
 authors = [{name = "Michael Kleehammer", email="[email protected]"}]
 
@@ -22,7 +22,6 @@
 classifiers=['Development Status :: 5 - Production/Stable',
              'Intended Audience :: Developers',
              'Intended Audience :: System Administrators',
-             'License :: OSI Approved :: MIT No Attribution License (MIT-0)',
              'Operating System :: Microsoft :: Windows',
              'Operating System :: POSIX',
              'Programming Language :: Python',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyodbc-5.2.0/setup.py new/pyodbc-5.3.0/setup.py
--- old/pyodbc-5.2.0/setup.py   2024-10-16 03:23:37.000000000 +0200
+++ new/pyodbc-5.3.0/setup.py   2025-10-17 19:30:17.000000000 +0200
@@ -56,11 +56,11 @@
         package_dir={'': 'src'},
         package_data={'': ['pyodbc.pyi']},  # places pyodbc.pyi alongside 
pyodbc.{platform}.{pyd|so} in site-packages
         license='MIT-0',
-        python_requires='>=3.8',
+        python_requires='>=3.9',
         classifiers=['Development Status :: 5 - Production/Stable',
                      'Intended Audience :: Developers',
                      'Intended Audience :: System Administrators',
-                     'License :: OSI Approved :: MIT No Attribution License 
(MIT-0)',
+                     'License :: OSI Approved :: MIT-0',
                      'Operating System :: Microsoft :: Windows',
                      'Operating System :: POSIX',
                      'Programming Language :: Python',
@@ -147,10 +147,15 @@
                 '/opt/homebrew/include',
                 expanduser('~/homebrew/include'),
             ]
+            _HOMEBREW_PREFIX = os.environ.get('HOMEBREW_PREFIX')
+            if _HOMEBREW_PREFIX is not None:
+                dirs.insert(0, os.path.join(_HOMEBREW_PREFIX, 'include'))
             settings['include_dirs'].extend(dir for dir in dirs if isdir(dir))
             # unixODBC make/install places libodbc.dylib in /usr/local/lib/ by 
default
             # ( also OS/X since El Capitan prevents /usr/lib from being 
accessed )
             settings['library_dirs'] = ['/usr/local/lib', '/opt/homebrew/lib']
+            if _HOMEBREW_PREFIX is not None:
+                settings['library_dirs'].insert(1, 
os.path.join(_HOMEBREW_PREFIX, 'lib'))
     else:
         # Other posix-like: Linux, Solaris, etc.
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyodbc-5.2.0/src/pyodbc.egg-info/PKG-INFO 
new/pyodbc-5.3.0/src/pyodbc.egg-info/PKG-INFO
--- old/pyodbc-5.2.0/src/pyodbc.egg-info/PKG-INFO       2024-10-16 
03:23:40.000000000 +0200
+++ new/pyodbc-5.3.0/src/pyodbc.egg-info/PKG-INFO       2025-10-17 
19:30:22.000000000 +0200
@@ -1,25 +1,58 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.4
 Name: pyodbc
-Version: 5.2.0
-Summary: DB API Module for ODBC
+Version: 5.3.0
+Summary: DB API module for ODBC
 Home-page: https://github.com/mkleehammer/pyodbc
+Author-email: Michael Kleehammer <[email protected]>
 Maintainer: Michael Kleehammer
-Maintainer-email: [email protected]
-License: MIT-0
-Platform: UNKNOWN
+Maintainer-email: Michael Kleehammer <[email protected]>
+License: MIT
+Project-URL: Homepage, https://github.com/mkleehammer/pyodbc
 Classifier: Development Status :: 5 - Production/Stable
 Classifier: Intended Audience :: Developers
 Classifier: Intended Audience :: System Administrators
-Classifier: License :: OSI Approved :: MIT No Attribution License (MIT-0)
 Classifier: Operating System :: Microsoft :: Windows
 Classifier: Operating System :: POSIX
 Classifier: Programming Language :: Python
 Classifier: Programming Language :: Python :: 3
 Classifier: Topic :: Database
-Requires-Python: >=3.8
+Requires-Python: >=3.9
+Description-Content-Type: text/markdown
 License-File: LICENSE.txt
+Dynamic: home-page
+Dynamic: license-file
+Dynamic: maintainer
+Dynamic: requires-python
 
-pyodbc is an open source Python module that makes accessing ODBC databases 
simple.
-It implements the [DB API 2.0](https://www.python.org/dev/peps/pep-0249)
-specification but is packed with even more Pythonic convenience.
+# pyodbc
 
+[![Windows 
build](https://ci.appveyor.com/api/projects/status/github/mkleehammer/pyodbc?branch=master&svg=true&passingText=Windows%20build&failingText=Windows%20build)](https://ci.appveyor.com/project/mkleehammer/pyodbc)
+[![Ubuntu 
build](https://github.com/mkleehammer/pyodbc/actions/workflows/ubuntu_build.yml/badge.svg)](https://github.com/mkleehammer/pyodbc/actions/workflows/ubuntu_build.yml)
+[![PyPI](https://img.shields.io/pypi/v/pyodbc?color=brightgreen)](https://pypi.org/project/pyodbc/)
+
+pyodbc is an open source Python module that makes accessing ODBC databases 
simple.  It
+implements the [DB API 2.0](https://www.python.org/dev/peps/pep-0249) 
specification but is packed with even more Pythonic convenience.
+
+The easiest way to install pyodbc is to use pip:
+
+    python -m pip install pyodbc
+
+On Macs, you should probably install unixODBC first if you don't already have 
an ODBC
+driver manager installed.  For example, using the [homebrew](https://brew.sh/) 
package manager:
+
+    brew install unixodbc
+    python -m pip install pyodbc
+
+Similarly, on Unix you should make sure you have an ODBC driver manager 
installed before
+installing pyodbc.  See the 
[docs](https://github.com/mkleehammer/pyodbc/wiki/Install)
+for more information about how to do this on different Unix flavors.  (On 
Windows, the
+ODBC driver manager is built-in.)
+
+Precompiled binary wheels are provided for multiple Python versions on most 
Windows, macOS,
+and Linux platforms.  On other platforms pyodbc will be built from the source 
code.  Note,
+pyodbc contains C++ extensions so you will need a suitable C++ compiler when 
building from
+source.  See the [docs](https://github.com/mkleehammer/pyodbc/wiki/Install) 
for details.
+
+[Documentation](https://github.com/mkleehammer/pyodbc/wiki)
+
+[Release Notes](https://github.com/mkleehammer/pyodbc/releases)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyodbc-5.2.0/src/pyodbc.egg-info/SOURCES.txt 
new/pyodbc-5.3.0/src/pyodbc.egg-info/SOURCES.txt
--- old/pyodbc-5.2.0/src/pyodbc.egg-info/SOURCES.txt    2024-10-16 
03:23:40.000000000 +0200
+++ new/pyodbc-5.3.0/src/pyodbc.egg-info/SOURCES.txt    2025-10-17 
19:30:22.000000000 +0200
@@ -37,4 +37,5 @@
 tests/conftest.py
 tests/mysql_test.py
 tests/postgresql_test.py
+tests/sqlite_test.py
 tests/sqlserver_test.py
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyodbc-5.2.0/src/pyodbcmodule.cpp 
new/pyodbc-5.3.0/src/pyodbcmodule.cpp
--- old/pyodbc-5.2.0/src/pyodbcmodule.cpp       2024-10-16 03:23:37.000000000 
+0200
+++ new/pyodbc-5.3.0/src/pyodbcmodule.cpp       2025-10-17 19:30:17.000000000 
+0200
@@ -1264,14 +1264,13 @@
     // parts
     //   A dictionary of text keywords and text values that will be appended.
 
-    assert(PyUnicode_Check(existing));
-
     Py_ssize_t pos = 0;
     PyObject* key = 0;
     PyObject* value = 0;
     Py_ssize_t length = 0;      // length in *characters*
     int result_kind = PyUnicode_1BYTE_KIND;
     if (existing) {
+        assert(PyUnicode_Check(existing));
         length = PyUnicode_GET_LENGTH(existing) + 1; // + 1 to add a trailing 
semicolon
         int kind = PyUnicode_KIND(existing);
         if (result_kind < kind)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyodbc-5.2.0/tests/conftest.py 
new/pyodbc-5.3.0/tests/conftest.py
--- old/pyodbc-5.2.0/tests/conftest.py  2024-10-16 03:23:37.000000000 +0200
+++ new/pyodbc-5.3.0/tests/conftest.py  2025-10-17 19:30:17.000000000 +0200
@@ -28,8 +28,8 @@
     Prepends the build directory to the path so that newly built pyodbc 
libraries are
     used, allowing it to be tested without pip-installing it.
     """
-    # look for the suffixes used in the build filenames, e.g. 
".cp38-win_amd64.pyd",
-    # ".cpython-38-darwin.so", ".cpython-38-x86_64-linux-gnu.so", etc.
+    # look for the suffixes used in the build filenames, e.g. 
".cp313-win_amd64.pyd",
+    # ".cpython-313-darwin.so", ".cpython-313-x86_64-linux-gnu.so", etc.
     library_exts = [ext for ext in importlib.machinery.EXTENSION_SUFFIXES if 
ext != '.pyd']
     # generate the name of the pyodbc build file(s)
     library_names = [f'pyodbc{ext}' for ext in library_exts]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyodbc-5.2.0/tests/sqlite_test.py 
new/pyodbc-5.3.0/tests/sqlite_test.py
--- old/pyodbc-5.2.0/tests/sqlite_test.py       1970-01-01 01:00:00.000000000 
+0100
+++ new/pyodbc-5.3.0/tests/sqlite_test.py       2025-10-17 19:30:17.000000000 
+0200
@@ -0,0 +1,638 @@
+#!/usr/bin/python
+
+usage = """\
+%(prog)s [options] connection_string
+
+Unit tests for SQLite using the ODBC driver from 
http://www.ch-werner.de/sqliteodbc
+
+To use, pass a connection string as the parameter. The tests will create and
+drop tables t1 and t2 as necessary.  On Windows, use the 32-bit driver with
+32-bit Python and the 64-bit driver with 64-bit Python (regardless of your
+operating system bitness).
+
+These run using the version from the 'build' directory, not the version
+installed into the Python directories.  You must run python setup.py build
+before running the tests.
+
+You can also put the connection string into a tmp/setup.cfg file like so:
+
+  [sqlitetests]
+  connection-string=Driver=SQLite3 ODBC Driver;Database=sqlite.db
+"""
+
+import sys, os, re
+import pytest
+import pyodbc
+from decimal import Decimal
+from datetime import datetime, date, time
+from os.path import join, getsize, dirname, abspath
+from typing import Iterator
+
+_TESTSTR = '0123456789-abcdefghijklmnopqrstuvwxyz-'
+
+def _generate_test_string(length):
+    """
+    Returns a string of `length` characters, constructed by repeating _TESTSTR 
as necessary.
+
+    To enhance performance, there are 3 ways data is read, based on the length 
of the value, so most data types are
+    tested with 3 lengths.  This function helps us generate the test data.
+
+    We use a recognizable data set instead of a single character to make it 
less likely that "overlap" errors will
+    be hidden and to help us manually identify where a break occurs.
+    """
+    if length <= len(_TESTSTR):
+        return _TESTSTR[:length]
+
+    c = (length + len(_TESTSTR)-1) // len(_TESTSTR)
+    v = _TESTSTR * c
+    return v[:length]
+
+SMALL_FENCEPOST_SIZES = [ 0, 1, 255, 256, 510, 511, 512, 1023, 1024, 2047, 
2048, 4000 ]
+LARGE_FENCEPOST_SIZES = [ 4095, 4096, 4097, 10 * 1024, 20 * 1024 ]
+
+STR_FENCEPOSTS = [ _generate_test_string(size) for size in 
SMALL_FENCEPOST_SIZES ]
+BYTE_FENCEPOSTS    = [ bytes(s, 'ascii') for s in STR_FENCEPOSTS ]
+IMAGE_FENCEPOSTS   = BYTE_FENCEPOSTS + [ bytes(_generate_test_string(size), 
'ascii') for size in LARGE_FENCEPOST_SIZES ]
+
[email protected]
+def connection_string(tmp_path):
+    return os.environ.get('PYODBC_SQLITE', 
f'driver=SQLite3;database={tmp_path}/test.db')
+
[email protected]()
+def cnxn(connection_string):
+    c = pyodbc.connect(connection_string, autocommit=False, attrs_before=None)
+    yield c
+
+    if not c.closed:
+        c.close()
+
[email protected]()
+def cursor(cnxn) -> Iterator[pyodbc.Cursor]:
+    cur = cnxn.cursor()
+
+    cur.execute("drop table if exists t0")
+    cur.execute("drop table if exists t1")
+    cur.execute("drop table if exists t2")
+    cnxn.commit()
+
+    yield cur
+
+def test_multiple_bindings(cursor):
+    "More than one bind and select on a cursor"
+    cursor.execute("create table t1(n int)")
+    cursor.execute("insert into t1 values (?)", 1)
+    cursor.execute("insert into t1 values (?)", 2)
+    cursor.execute("insert into t1 values (?)", 3)
+    for i in range(3):
+        cursor.execute("select n from t1 where n < ?", 10)
+        cursor.execute("select n from t1 where n < 3")
+
+
+def test_different_bindings(cursor):
+    cursor.execute("create table t1(n int)")
+    cursor.execute("create table t2(d datetime)")
+    cursor.execute("insert into t1 values (?)", 1)
+    cursor.execute("insert into t2 values (?)", datetime.now())
+
+def test_drivers():
+    p = pyodbc.drivers()
+    assert isinstance(p, list)
+
+def test_datasources():
+    p = pyodbc.dataSources()
+    assert isinstance(p, dict)
+
+def test_getinfo_string(cnxn):
+    value = cnxn.getinfo(pyodbc.SQL_CATALOG_NAME_SEPARATOR)
+    assert isinstance(value, str)
+
+def test_getinfo_bool(cnxn):
+    value = cnxn.getinfo(pyodbc.SQL_ACCESSIBLE_TABLES)
+    assert isinstance(value, bool)
+
+def test_getinfo_int(cnxn):
+    value = cnxn.getinfo(pyodbc.SQL_DEFAULT_TXN_ISOLATION)
+    assert isinstance(value, int)
+
+def test_getinfo_smallint(cnxn):
+    value = cnxn.getinfo(pyodbc.SQL_CONCAT_NULL_BEHAVIOR)
+    assert isinstance(value, int)
+
+def _test_strtype(cursor, sqltype, value, colsize=None):
+    """
+    The implementation for string, Unicode, and binary tests.
+    """
+    assert colsize is None or (value is None or colsize >= len(value))
+
+    if colsize:
+        sql = "create table t1(s {}({}))".format(sqltype, colsize)
+    else:
+        sql = "create table t1(s %s)" % sqltype
+
+    cursor.execute(sql)
+    cursor.execute("insert into t1 values(?)", value)
+    v = cursor.execute("select * from t1").fetchone()[0]
+    assert type(v) == type(value)
+
+    if value is not None:
+        assert len(v) == len(value)
+
+    assert v == value
+
+    # Reported by Andy Hochhaus in the pyodbc group: In 2.1.7 and earlier, a 
hardcoded length of 255 was used to
+    # determine whether a parameter was bound as a SQL_VARCHAR or 
SQL_LONGVARCHAR.  Apparently SQL Server chokes if
+    # we bind as a SQL_LONGVARCHAR and the target column size is 8000 or less, 
which is considers just SQL_VARCHAR.
+    # This means binding a 256 character value would cause problems if 
compared with a VARCHAR column under
+    # 8001. We now use SQLGetTypeInfo to determine the time to switch.
+    #
+    # [42000] [Microsoft][SQL Server Native Client 10.0][SQL Server]The data 
types varchar and text are incompatible in the equal to operator.
+
+    cursor.execute("select * from t1 where s=?", value)
+
+
+def _test_strliketype(cursor, sqltype, value, colsize=None):
+    """
+    The implementation for text, image, ntext, and binary.
+
+    These types do not support comparison operators.
+    """
+    assert colsize is None or (value is None or colsize >= len(value))
+
+    if colsize:
+        sql = "create table t1(s {}({}))".format(sqltype, colsize)
+    else:
+        sql = "create table t1(s %s)" % sqltype
+
+    cursor.execute(sql)
+    cursor.execute("insert into t1 values(?)", value)
+    v = cursor.execute("select * from t1").fetchone()[0]
+    assert type(v) == type(value)
+
+    if value is not None:
+        assert len(v) == len(value)
+
+    assert v == value
+
+#
+# text
+#
+
+def test_text_null(cursor):
+    _test_strtype(cursor, 'text', None, 100)
+
+# Generate a test for each fencepost size: test_text_0, etc.
+def _maketest(value):
+    def t(cursor):
+        _test_strtype(cursor, 'text', value, len(value))
+    return t
+for value in STR_FENCEPOSTS:
+    locals()['test_text_%s' % len(value)] = _maketest(value)
+
+def test_text_upperlatin(cursor):
+    _test_strtype(cursor, 'varchar', 'รก')
+
+#
+# blob
+#
+
+def test_null_blob(cursor):
+    _test_strtype(cursor, 'blob', None, 100)
+
+def test_large_null_blob(cursor):
+    # Bug 1575064
+    _test_strtype(cursor, 'blob', None, 4000)
+
+# Generate a test for each fencepost size: test_unicode_0, etc.
+def _maketest(value):
+    def t(cursor):
+        _test_strtype(cursor, 'blob', value, len(value))
+    return t
+for value in BYTE_FENCEPOSTS:
+    locals()['test_blob_%s' % len(value)] = _maketest(value)
+
+def test_subquery_params(cursor):
+    """Ensure parameter markers work in a subquery"""
+    cursor.execute("create table t1(id integer, s varchar(20))")
+    cursor.execute("insert into t1 values (?,?)", 1, 'test')
+    row = cursor.execute("""
+                              select x.id
+                              from (
+                                select id
+                                from t1
+                                where s = ?
+                                  and id between ? and ?
+                               ) x
+                               """, 'test', 1, 10).fetchone()
+    assert row != None
+    assert row[0] == 1
+
+def test_close_cnxn(cursor, cnxn):
+    """Make sure using a Cursor after closing its connection doesn't crash."""
+
+    cursor.execute("create table t1(id integer, s varchar(20))")
+    cursor.execute("insert into t1 values (?,?)", 1, 'test')
+    cursor.execute("select * from t1")
+
+    cnxn.close()
+
+    # Now that the connection is closed, we expect an exception.  (If the code 
attempts to use
+    # the HSTMT, we'll get an access violation instead.)
+    sql = "select * from t1"
+    with pytest.raises(pyodbc.ProgrammingError):
+        cursor.execute(sql)
+
+def test_negative_row_index(cursor):
+    cursor.execute("create table t1(s varchar(20))")
+    cursor.execute("insert into t1 values(?)", "1")
+    row = cursor.execute("select * from t1").fetchone()
+    assert row[0] == "1"
+    assert row[-1] == "1"
+
+def test_version(cursor):
+    assert 3 == len(pyodbc.version.split('.')) # 1.3.1 etc.
+
+#
+# ints and floats
+#
+
+def test_int(cursor):
+    value = 1234
+    cursor.execute("create table t1(n int)")
+    cursor.execute("insert into t1 values (?)", value)
+    result = cursor.execute("select n from t1").fetchone()[0]
+    assert result == value
+
+def test_negative_int(cursor):
+    value = -1
+    cursor.execute("create table t1(n int)")
+    cursor.execute("insert into t1 values (?)", value)
+    result = cursor.execute("select n from t1").fetchone()[0]
+    assert result == value
+
+def test_bigint(cursor):
+    input = 3000000000
+    cursor.execute("create table t1(d bigint)")
+    cursor.execute("insert into t1 values (?)", input)
+    result = cursor.execute("select d from t1").fetchone()[0]
+    assert result == input
+
+def test_negative_bigint(cursor):
+    # Issue 186: BIGINT problem on 32-bit architecture
+    input = -430000000
+    cursor.execute("create table t1(d bigint)")
+    cursor.execute("insert into t1 values (?)", input)
+    result = cursor.execute("select d from t1").fetchone()[0]
+    assert result == input
+
+def test_float(cursor):
+    value = 1234.567
+    cursor.execute("create table t1(n float)")
+    cursor.execute("insert into t1 values (?)", value)
+    result = cursor.execute("select n from t1").fetchone()[0]
+    assert result == value
+
+def test_negative_float(cursor):
+    value = -200
+    cursor.execute("create table t1(n float)")
+    cursor.execute("insert into t1 values (?)", value)
+    result  = cursor.execute("select n from t1").fetchone()[0]
+    assert value == result
+
+#
+# rowcount
+#
+
+# Note: SQLRowCount does not define what the driver must return after a select 
statement
+# and says that its value should not be relied upon.  The sqliteodbc driver is 
hardcoded to
+# return 0 so I've deleted the test.
+
+def test_rowcount_delete(cursor):
+    assert cursor.rowcount == 0
+    cursor.execute("create table t1(i int)")
+    count = 4
+    for i in range(count):
+        cursor.execute("insert into t1 values (?)", i)
+    cursor.execute("delete from t1")
+    assert cursor.rowcount == count
+
+def test_rowcount_nodata(cursor):
+    """
+    This represents a different code path than a delete that deleted something.
+
+    The return value is SQL_NO_DATA and code after it was causing an error.  
We could use SQL_NO_DATA to step over
+    the code that errors out and drop down to the same SQLRowCount code.  On 
the other hand, we could hardcode a
+    zero return value.
+    """
+    cursor.execute("create table t1(i int)")
+    # This is a different code path internally.
+    cursor.execute("delete from t1")
+    assert cursor.rowcount == 0
+
+# In the 2.0.x branch, Cursor.execute sometimes returned the cursor and 
sometimes the rowcount.  This proved very
+# confusing when things went wrong and added very little value even when 
things went right since users could always
+# use: cursor.execute("...").rowcount
+
+def test_retcursor_delete(cursor):
+    cursor.execute("create table t1(i int)")
+    cursor.execute("insert into t1 values (1)")
+    v = cursor.execute("delete from t1")
+    assert v == cursor
+
+def test_retcursor_nodata(cursor):
+    """
+    This represents a different code path than a delete that deleted something.
+
+    The return value is SQL_NO_DATA and code after it was causing an error.  
We could use SQL_NO_DATA to step over
+    the code that errors out and drop down to the same SQLRowCount code.
+    """
+    cursor.execute("create table t1(i int)")
+    # This is a different code path internally.
+    v = cursor.execute("delete from t1")
+    assert v == cursor
+
+def test_retcursor_select(cursor):
+    cursor.execute("create table t1(i int)")
+    cursor.execute("insert into t1 values (1)")
+    v = cursor.execute("select * from t1")
+    assert v == cursor
+
+#
+# misc
+#
+
+def test_lower_case(cnxn):
+    "Ensure pyodbc.lowercase forces returned column names to lowercase."
+
+    # Has to be set before creating the cursor, so we must recreate cursor.
+
+    pyodbc.lowercase = True
+    cursor = cnxn.cursor()
+
+    cursor.execute("create table t1(Abc int, dEf int)")
+    cursor.execute("select * from t1")
+
+    names = [ t[0] for t in cursor.description ]
+    names.sort()
+
+    assert names == [ "abc", "def" ]
+
+    # Put it back so other tests don't fail.
+    pyodbc.lowercase = False
+
+def test_row_description(cnxn):
+    """
+    Ensure Cursor.description is accessible as Row.cursor_description.
+    """
+    cursor = cnxn.cursor()
+    cursor.execute("create table t1(a int, b char(3))")
+    cnxn.commit()
+    cursor.execute("insert into t1 values(1, 'abc')")
+
+    row = cursor.execute("select * from t1").fetchone()
+
+    assert cursor.description == row.cursor_description
+
+
+def test_executemany(cursor):
+    cursor.execute("create table t1(a int, b varchar(10))")
+
+    params = [ (i, str(i)) for i in range(1, 6) ]
+
+    cursor.executemany("insert into t1(a, b) values (?,?)", params)
+
+    count = cursor.execute("select count(*) from t1").fetchone()[0]
+    assert count == len(params)
+
+    cursor.execute("select a, b from t1 order by a")
+    rows = cursor.fetchall()
+    assert count == len(rows)
+
+    for param, row in zip(params, rows):
+        assert param[0] == row[0]
+        assert param[1] == row[1]
+
+
+def test_executemany_one(cursor):
+    "Pass executemany a single sequence"
+    cursor.execute("create table t1(a int, b varchar(10))")
+
+    params = [ (1, "test") ]
+
+    cursor.executemany("insert into t1(a, b) values (?,?)", params)
+
+    count = cursor.execute("select count(*) from t1").fetchone()[0]
+    assert count == len(params)
+
+    cursor.execute("select a, b from t1 order by a")
+    rows = cursor.fetchall()
+    assert count == len(rows)
+
+    for param, row in zip(params, rows):
+        assert param[0] == row[0]
+        assert param[1] == row[1]
+
+
+def test_executemany_failure(cursor):
+    """
+    Ensure that an exception is raised if one query in an executemany fails.
+    """
+    cursor.execute("create table t1(a int, b varchar(10))")
+
+    params = [ (1, 'good'),
+               ('error', 'not an int'),
+               (3, 'good') ]
+
+    with pytest.raises(pyodbc.Error):
+        cursor.executemany("insert into t1(a, b) value (?, ?)", params)
+
+
+def test_row_slicing(cursor):
+    cursor.execute("create table t1(a int, b int, c int, d int)");
+    cursor.execute("insert into t1 values(1,2,3,4)")
+
+    row = cursor.execute("select * from t1").fetchone()
+
+    result = row[:]
+    assert result is row
+
+    result = row[:-1]
+    assert result == (1,2,3)
+
+    result = row[0:4]
+    assert result is row
+
+
+def test_row_repr(cursor):
+    cursor.execute("create table t1(a int, b int, c int, d int)");
+    cursor.execute("insert into t1 values(1,2,3,4)")
+
+    row = cursor.execute("select * from t1").fetchone()
+
+    result = str(row)
+    assert result == "(1, 2, 3, 4)"
+
+    result = str(row[:-1])
+    assert result == "(1, 2, 3)"
+
+    result = str(row[:1])
+    assert result == "(1,)"
+
+
+def test_view_select(cursor):
+    # Reported in forum: Can't select from a view?  I think I do this a lot, 
but another test never hurts.
+
+    # Create a table (t1) with 3 rows and a view (t2) into it.
+    cursor.execute("create table t1(c1 int identity(1, 1), c2 varchar(50))")
+    for i in range(3):
+        cursor.execute("insert into t1(c2) values (?)", "string%s" % i)
+    cursor.execute("create view t2 as select * from t1")
+
+    # Select from the view
+    cursor.execute("select * from t2")
+    rows = cursor.fetchall()
+    assert rows is not None
+    assert len(rows) == 3
+
+def test_autocommit(cnxn, connection_string):
+    assert cnxn.autocommit == False
+
+    othercnxn = pyodbc.connect(connection_string, autocommit=True)
+    assert othercnxn.autocommit == True
+
+    othercnxn.autocommit = False
+    assert othercnxn.autocommit == False
+
+def test_skip(cursor):
+    # Insert 1, 2, and 3.  Fetch 1, skip 2, fetch 3.
+
+    cursor.execute("create table t1(id int)");
+    for i in range(1, 5):
+        cursor.execute("insert into t1 values(?)", i)
+    cursor.execute("select id from t1 order by id")
+    assert cursor.fetchone()[0] == 1
+    cursor.skip(2)
+    assert cursor.fetchone()[0] == 4
+
+def test_sets_execute(cursor):
+    # Only lists and tuples are allowed.
+    with pytest.raises(pyodbc.ProgrammingError):
+        cursor.execute("create table t1 (word varchar (100))")
+        words = set (['a'])
+        cursor.execute("insert into t1 (word) VALUES (?)", [words])
+
+def test_sets_executemany(cursor):
+    # Only lists and tuples are allowed.
+    with pytest.raises(TypeError):
+        cursor.execute("create table t1 (word varchar (100))")
+        words = set (['a'])
+        cursor.executemany("insert into t1 (word) values (?)", [words])
+
+def test_row_execute(cursor):
+    "Ensure we can use a Row object as a parameter to execute"
+    cursor.execute("create table t1(n int, s varchar(10))")
+    cursor.execute("insert into t1 values (1, 'a')")
+    row = cursor.execute("select n, s from t1").fetchone()
+    assert row != None
+
+    cursor.execute("create table t2(n int, s varchar(10))")
+    cursor.execute("insert into t2 values (?, ?)", row)
+
+def test_row_executemany(cursor):
+    "Ensure we can use a Row object as a parameter to executemany"
+    cursor.execute("create table t1(n int, s varchar(10))")
+
+    for i in range(3):
+        cursor.execute("insert into t1 values (?, ?)", i, chr(ord('a')+i))
+
+    rows = cursor.execute("select n, s from t1").fetchall()
+    assert len(rows) != 0
+
+    cursor.execute("create table t2(n int, s varchar(10))")
+    cursor.executemany("insert into t2 values (?, ?)", rows)
+
+def test_description(cursor):
+    "Ensure cursor.description is correct"
+
+    cursor.execute("create table t1(n int, s text)")
+    cursor.execute("insert into t1 values (1, 'abc')")
+    cursor.execute("select * from t1")
+
+    # (I'm not sure the precision of an int is constant across different 
versions, bits, so I'm hand checking the
+    # items I do know.
+
+    # int
+    t = cursor.description[0]
+    assert t[0] == 'n'
+    assert t[1] == int
+    assert t[5] == 0       # scale
+    assert t[6] == True    # nullable
+
+    # text
+    t = cursor.description[1]
+    assert t[0] == 's'
+    assert t[1] == str
+    assert t[5] == 0       # scale
+    assert t[6] == True    # nullable
+
+def test_row_equal(cursor):
+    cursor.execute("create table t1(n int, s varchar(20))")
+    cursor.execute("insert into t1 values (1, 'test')")
+    row1 = cursor.execute("select n, s from t1").fetchone()
+    row2 = cursor.execute("select n, s from t1").fetchone()
+    b = (row1 == row2)
+    assert b == True
+
+def test_row_gtlt(cursor):
+    cursor.execute("create table t1(n int, s varchar(20))")
+    cursor.execute("insert into t1 values (1, 'test1')")
+    cursor.execute("insert into t1 values (1, 'test2')")
+    rows = cursor.execute("select n, s from t1 order by s").fetchall()
+    assert rows[0] < rows[1]
+    assert rows[0] <= rows[1]
+    assert rows[1] > rows[0]
+    assert rows[1] >= rows[0]
+    assert rows[0] != rows[1]
+
+    rows = list(rows)
+    rows.sort() # uses <
+
+def _test_context_manager(connection_string):
+    # TODO: This is failing, but it may be due to the design of sqlite.  I've 
disabled it
+    # for now until I can research it some more.
+
+    # WARNING: This isn't working right now.  We've set the driver's 
autocommit to "off",
+    # but that doesn't automatically start a transaction.  I'm not familiar 
enough with the
+    # internals of the driver to tell what is going on, but it looks like 
there is support
+    # for the autocommit flag.
+    #
+    # I thought it might be a timing issue, like it not actually starting a 
txn until you
+    # try to do something, but that doesn't seem to work either.  I'll leave 
this in to
+    # remind us that it isn't working yet but we need to contact the SQLite 
ODBC driver
+    # author for some guidance.
+
+    with pyodbc.connect(connection_string) as cnxn:
+        cursor = cnxn.cursor()
+        cursor.execute("begin")
+        cursor.execute("create table t1(i int)")
+        cursor.execute('rollback')
+
+    # The connection should be closed now.
+    with pytest.raises(pyodbc.Error):
+        cnxn.execute('rollback')
+
+def test_untyped_none(cursor):
+    # From issue 129
+    value = cursor.execute("select ?", None).fetchone()[0]
+    assert value == None
+
+def test_large_update_nodata(cursor):
+    cursor.execute('create table t1(a blob)')
+    hundredkb = 'x'*100*1024
+    cursor.execute('update t1 set a=? where 1=0', (hundredkb,))
+
+def test_no_fetch(cursor):
+    # Issue 89 with FreeTDS: Multiple selects (or catalog functions that issue 
selects) without fetches seem to
+    # confuse the driver.
+    cursor.execute('select 1')
+    cursor.execute('select 1')
+    cursor.execute('select 1')

Reply via email to