Skip to content

Commit

Permalink
Merge branch 'master' into verify_312
Browse files Browse the repository at this point in the history
  • Loading branch information
jettify authored Oct 28, 2023
2 parents f7dbf23 + 53fe8a6 commit feb466d
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 171 deletions.
8 changes: 8 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
Changes
-------
0.4.1 (2023-10-28)
^^^^^^^^^^^^^^^^^^
* Implemented cursor setinputsizes.
* Implemented cursor fetchval.
* Added more type annotations.
* Added autocommit setter for cusror.


0.4.0 (2023-03-16)
^^^^^^^^^^^^^^^^^^
* Fixed compatibility with python 3.9+.
Expand Down
28 changes: 25 additions & 3 deletions aioodbc/cursor.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,21 @@ def executemany(self, sql, *params):
def callproc(self, procname, args=()):
raise NotImplementedError

async def setinputsizes(self, *args, **kwargs):
"""Does nothing, required by DB API."""
return None
async def setinputsizes(self, sizes=None) -> None:
"""Explicitly declare the types and sizes of the parameters in a query.
Set to None to clear any previously registered input sizes.
:param sizes: A list of tuples, one tuple for each query parameter,
where each tuple contains:
1. the column datatype
2. the column size (char length or decimal precision)
3. the decimal scale.
For example:
[(pyodbc.SQL_WVARCHAR, 50, 0), (pyodbc.SQL_DECIMAL, 18, 4)]
"""
# sizes: Optional[Iterable[Tuple[int, int, int]]]
await self._run_operation(self._impl.setinputsizes, sizes)

async def setoutputsize(self, *args, **kwargs):
"""Does nothing, required by DB API."""
Expand All @@ -163,6 +175,16 @@ def fetchone(self):
fut = self._run_operation(self._impl.fetchone)
return fut

def fetchval(self):
"""Returns the first column of the first row if there are results.
A ProgrammingError exception is raised if no SQL has been executed
or if it did not return a result set (e.g. was not a SELECT
statement).
"""
fut = self._run_operation(self._impl.fetchval)
return fut

def fetchall(self):
"""Returns a list of all remaining rows.
Expand Down
5 changes: 2 additions & 3 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
-e .
aiodocker==0.21.0
bandit==1.7.5
black==23.3.0
flake8-bugbear==23.3.23
Expand All @@ -8,7 +7,7 @@ ipdb==0.13.13
ipython==8.13.2
isort==5.12.0
mypy==1.2.0
pyodbc>=5.0.0b3
pyodbc==5.0.1
pytest-asyncio==0.21.0
pytest-cov==4.0.0
pytest-faulthandler==2.0.1
Expand All @@ -17,4 +16,4 @@ pytest==7.3.1
sphinx==7.0.0
sphinxcontrib-asyncio==0.3.0
twine==4.0.2
uvloop==0.17.0
uvloop==0.19.0
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

from setuptools import find_packages, setup

install_requires = ["pyodbc>=5.0.0b3"]

install_requires = ["pyodbc>=5.0.1"]


def read(f):
Expand Down
164 changes: 1 addition & 163 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
import asyncio
import gc
import os
import random
import time
import uuid
from concurrent.futures import ThreadPoolExecutor
from contextlib import asynccontextmanager

import pyodbc
import pytest
import pytest_asyncio
import uvloop
from aiodocker import Docker

import aioodbc

Expand Down Expand Up @@ -43,163 +38,6 @@ def loop(event_loop):
return event_loop


@pytest.fixture(scope="session")
async def docker():
client = Docker()

try:
yield client
finally:
await client.close()


@pytest.fixture(scope="session")
def host():
# Alternative: host.docker.internal, however not working on travis
return os.environ.get("DOCKER_MACHINE_IP", "127.0.0.1")


@pytest_asyncio.fixture
async def pg_params(pg_server):
server_info = pg_server["pg_params"]
return dict(**server_info)


@asynccontextmanager
async def _pg_server_helper(host, docker, session_id):
pg_tag = "9.5"

await docker.pull(f"postgres:{pg_tag}")
container = await docker.containers.create_or_replace(
name=f"aioodbc-test-server-{pg_tag}-{session_id}",
config={
"Image": f"postgres:{pg_tag}",
"AttachStdout": False,
"AttachStderr": False,
"HostConfig": {
"PublishAllPorts": True,
},
},
)
await container.start()
container_port = await container.port(5432)
port = container_port[0]["HostPort"]

pg_params = {
"database": "postgres",
"user": "postgres",
"password": "mysecretpassword",
"host": host,
"port": port,
}

start = time.time()
dsn = create_pg_dsn(pg_params)
last_error = None
container_info = {
"port": port,
"pg_params": pg_params,
"container": container,
"dsn": dsn,
}
try:
while (time.time() - start) < 40:
try:
conn = pyodbc.connect(dsn)
cur = conn.execute("SELECT 1;")
cur.close()
conn.close()
break
except pyodbc.Error as e:
last_error = e
await asyncio.sleep(random.uniform(0.1, 1))
else:
pytest.fail(f"Cannot start postgres server: {last_error}")

yield container_info
finally:
container = container_info["container"]
if container:
await container.kill()
await container.delete(v=True, force=True)


@pytest.fixture(scope="session")
async def pg_server(host, docker, session_id):
async with _pg_server_helper(host, docker, session_id) as helper:
yield helper


@pytest.fixture
async def pg_server_local(host, docker):
async with _pg_server_helper(host, docker, None) as helper:
yield helper


@pytest.fixture
async def mysql_params(mysql_server):
server_info = (mysql_server)["mysql_params"]
return dict(**server_info)


@pytest.fixture(scope="session")
async def mysql_server(host, docker, session_id):
mysql_tag = "5.7"
await docker.pull(f"mysql:{mysql_tag}")
container = await docker.containers.create_or_replace(
name=f"aioodbc-test-server-{mysql_tag}-{session_id}",
config={
"Image": f"mysql:{mysql_tag}",
"AttachStdout": False,
"AttachStderr": False,
"Env": [
"MYSQL_USER=aioodbc",
"MYSQL_PASSWORD=mysecretpassword",
"MYSQL_DATABASE=aioodbc",
"MYSQL_ROOT_PASSWORD=mysecretpassword",
],
"HostConfig": {
"PublishAllPorts": True,
},
},
)
await container.start()
port = (await container.port(3306))[0]["HostPort"]
mysql_params = {
"database": "aioodbc",
"user": "aioodbc",
"password": "mysecretpassword",
"host": host,
"port": port,
}
dsn = create_mysql_dsn(mysql_params)
start = time.time()
try:
last_error = None
while (time.time() - start) < 30:
try:
conn = pyodbc.connect(dsn)
cur = conn.execute("SELECT 1;")
cur.close()
conn.close()
break
except pyodbc.Error as e:
last_error = e
await asyncio.sleep(random.uniform(0.1, 1))
else:
pytest.fail(f"Cannot start mysql server: {last_error}")

container_info = {
"port": port,
"mysql_params": mysql_params,
}

yield container_info
finally:
await container.kill()
await container.delete(v=True, force=True)


@pytest.fixture
def executor():
executor = ThreadPoolExecutor(max_workers=1)
Expand All @@ -215,7 +53,7 @@ def pytest_configure():


@pytest.fixture
def db(request):
def db():
return "sqlite"


Expand Down
18 changes: 17 additions & 1 deletion tests/test_cursor.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,11 @@ async def test_cursor(conn):
assert cur.arraysize == 1
assert cur.rowcount == -1

r = await cur.setinputsizes()
r = await cur.setinputsizes(
[
(pyodbc.SQL_WVARCHAR, 50, 0),
]
)
assert r is None

await cur.setoutputsize()
Expand Down Expand Up @@ -172,6 +176,18 @@ async def test_fetchone(conn, table):
await cur.close()


@pytest.mark.parametrize("db", pytest.db_list)
@pytest.mark.asyncio
async def test_fetchval(conn, table):
cur = await conn.cursor()
await cur.execute("SELECT * FROM t1;")
resp = await cur.fetchval()
expected = 1

assert expected == resp
await cur.close()


@pytest.mark.parametrize("db", ["sqlite"])
@pytest.mark.asyncio
async def test_tables(conn, table):
Expand Down

0 comments on commit feb466d

Please sign in to comment.