fail-fast: false
matrix:
arch: [x86_64, i686, ppc64le, aarch64]
- pyver: [cp36, cp37, cp38, cp39, cp310]
+ pyver: [cp37, cp38, cp39, cp310]
platform: [manylinux, musllinux]
steps:
matrix:
# These archs require an Apple M1 runner: [arm64, universal2]
arch: [x86_64]
- pyver: [cp36, cp37, cp38, cp39, cp310]
+ pyver: [cp37, cp38, cp39, cp310]
steps:
- uses: actions/checkout@v2
matrix:
# Might want to add win32, untested at the moment.
arch: [win_amd64]
- pyver: [cp36, cp37, cp38, cp39, cp310]
+ pyver: [cp37, cp38, cp39, cp310]
steps:
- uses: actions/checkout@v2
matrix:
include:
# Test different combinations of Python, Postgres, libpq.
- - {impl: python, python: "3.6", postgres: "postgres:10", libpq: "newest"}
- - {impl: python, python: "3.7", postgres: "postgres:11", libpq: ""}
+ - {impl: python, python: "3.7", postgres: "postgres:10", libpq: "newest"}
- {impl: python, python: "3.8", postgres: "postgres:12", libpq: ""}
- {impl: python, python: "3.9", postgres: "postgres:13", libpq: "newest"}
- {impl: python, python: "3.10", postgres: "postgres:14", libpq: "oldest"}
- - {impl: c, python: "3.6", postgres: "postgres:14", libpq: "oldest"}
- - {impl: c, python: "3.7", postgres: "postgres:13", libpq: ""}
- - {impl: c, python: "3.8", postgres: "postgres:12", libpq: "newest"}
- - {impl: c, python: "3.9", postgres: "postgres:11", libpq: ""}
+ - {impl: c, python: "3.7", postgres: "postgres:14", libpq: "oldest"}
+ - {impl: c, python: "3.8", postgres: "postgres:13", libpq: "newest"}
+ - {impl: c, python: "3.9", postgres: "postgres:12", libpq: ""}
- {impl: c, python: "3.10", postgres: "postgres:10", libpq: "oldest"}
- {impl: dns, python: "3.9", postgres: "postgres:14", libpq: ""}
- {impl: postgis, python: "3.9", postgres: "postgis/postgis", libpq: ""}
fail-fast: false
matrix:
include:
- - {impl: python, python: "3.6"}
- {impl: python, python: "3.7"}
- {impl: python, python: "3.8"}
- {impl: python, python: "3.9"}
- {impl: python, python: "3.10"}
- - {impl: c, python: "3.6"}
- {impl: c, python: "3.7"}
- {impl: c, python: "3.8"}
- {impl: c, python: "3.9"}
fail-fast: false
matrix:
include:
- - {impl: python, python: "3.6"}
- {impl: python, python: "3.7"}
- {impl: python, python: "3.8"}
- {impl: python, python: "3.9"}
- {impl: python, python: "3.10"}
- - {impl: c, python: "3.6"}
- {impl: c, python: "3.7"}
- {impl: c, python: "3.8"}
- {impl: c, python: "3.9"}
The Psycopg version documented here has *official and tested* support for:
-- Python: from version 3.6 to 3.10
+- Python: from version 3.7 to 3.10
+
+ - Python 3.6 supported before Psycopg 3.1
+
- PostgreSQL: from version 10 to 14
- OS: Linux, macOS, Windows
result set can be accessed by calling `~Cursor.nextset()` (:ticket:`#164`).
- Add `pq.PGconn.trace()` and related trace functions (:ticket:`#167`).
- Add *prepare_threshold* parameter to `Connection` init (:ticket:`#200`).
+- Drop support for Python 3.6.
Current release
T = TypeVar("T")
FutureT = Union["asyncio.Future[T]", Generator[Any, None, T], Awaitable[T]]
-if sys.version_info >= (3, 7):
- from contextlib import asynccontextmanager
-
- get_running_loop = asyncio.get_running_loop
-
-else:
- from ._context import asynccontextmanager
-
- get_running_loop = asyncio.get_event_loop
-
-
if sys.version_info >= (3, 8):
create_task = asyncio.create_task
-elif sys.version_info >= (3, 7):
-
- def create_task(
- coro: FutureT[T], name: Optional[str] = None
- ) -> "asyncio.Future[T]":
- return asyncio.create_task(coro)
-
else:
def create_task(
coro: FutureT[T], name: Optional[str] = None
) -> "asyncio.Future[T]":
- return asyncio.ensure_future(coro)
+ return asyncio.create_task(coro)
if sys.version_info >= (3, 9):
"Deque",
"Protocol",
"ZoneInfo",
- "asynccontextmanager",
"create_task",
- "get_running_loop",
]
+++ /dev/null
-# type: ignore
-"""
-asynccontextmanager implementation for Python < 3.7
-"""
-
-from functools import wraps
-
-
-def asynccontextmanager(func):
- @wraps(func)
- def helper(*args, **kwds):
- return _AsyncGeneratorContextManager(func, args, kwds)
-
- return helper
-
-
-class _AsyncGeneratorContextManager:
- """Helper for @asynccontextmanager."""
-
- def __init__(self, func, args, kwds):
- self.gen = func(*args, **kwds)
- self.func, self.args, self.kwds = func, args, kwds
- doc = getattr(func, "__doc__", None)
- if doc is None:
- doc = type(self).__doc__
- self.__doc__ = doc
-
- async def __aenter__(self):
- try:
- return await self.gen.__anext__()
- except StopAsyncIteration:
- raise RuntimeError("generator didn't yield") from None
-
- async def __aexit__(self, typ, value, traceback):
- if typ is None:
- try:
- await self.gen.__anext__()
- except StopAsyncIteration:
- return
- else:
- raise RuntimeError("generator didn't stop")
- else:
- if value is None:
- value = typ()
- try:
- await self.gen.athrow(typ, value, traceback)
- raise RuntimeError("generator didn't stop after athrow()")
- except StopAsyncIteration as exc:
- return exc is not value
- except RuntimeError as exc:
- if exc is value:
- return False
- if isinstance(value, (StopIteration, StopAsyncIteration)):
- if exc.__cause__ is value:
- return False
- raise
- except BaseException as exc:
- if exc is not value:
- raise
from types import TracebackType
from typing import Any, AsyncGenerator, AsyncIterator, Dict, List, Optional
from typing import Type, Union, cast, overload, TYPE_CHECKING
+from contextlib import asynccontextmanager
from . import errors as e
from . import waiting
from .rows import Row, AsyncRowFactory, tuple_row, TupleRow, args_row
from .adapt import AdaptersMap
from ._enums import IsolationLevel
-from ._compat import asynccontextmanager, get_running_loop
from .conninfo import make_conninfo, conninfo_to_dict
from ._encodings import pgconn_encoding
from .connection import BaseConnection, CursorRow, Notify
) -> "AsyncConnection[Any]":
if sys.platform == "win32":
- loop = get_running_loop()
+ loop = asyncio.get_running_loop()
if isinstance(loop, asyncio.ProactorEventLoop):
raise e.InterfaceError(
"Psycopg cannot use the 'ProactorEventLoop' to run in async"
# Copyright (C) 2020 The Psycopg Team
-import sys
from types import TracebackType
from typing import Any, Generic, Iterable, Iterator, List
from typing import Optional, NoReturn, Sequence, Type, TypeVar, TYPE_CHECKING
class BaseCursor(Generic[ConnectionType, Row]):
- # Slots with __weakref__ and generic bases don't work on Py 3.6
- # https://bugs.python.org/issue41451
- if sys.version_info >= (3, 7):
- __slots__ = """
- _conn format _adapters arraysize _closed _results pgresult _pos
- _iresult _rowcount _query _tx _last_query _row_factory _make_row
- _pgconn _encoding
- __weakref__
- """.split()
+ __slots__ = """
+ _conn format _adapters arraysize _closed _results pgresult _pos
+ _iresult _rowcount _query _tx _last_query _row_factory _make_row
+ _pgconn _encoding
+ __weakref__
+ """.split()
ExecStatus = pq.ExecStatus
from types import TracebackType
from typing import Any, AsyncIterator, Iterable, List
from typing import Optional, Type, TypeVar, TYPE_CHECKING
+from contextlib import asynccontextmanager
from . import errors as e
from .copy import AsyncCopy
from .rows import Row, RowMaker, AsyncRowFactory
from .cursor import BaseCursor
-from ._compat import asynccontextmanager
if TYPE_CHECKING:
from .connection_async import AsyncConnection
# Copyright (C) 2020 The Psycopg Team
import re
-import sys
import struct
from datetime import date, datetime, time, timedelta, timezone
from typing import Any, Callable, cast, Optional, Tuple, TYPE_CHECKING
class TimetzLoader(Loader):
- _py37 = sys.version_info >= (3, 7)
-
_re_format = re.compile(
rb"""(?ix)
^
off = 60 * 60 * int(oh)
if om:
off += 60 * int(om)
- if os and self._py37:
+ if os:
off += int(os)
tz = timezone(timedelta(0, off if sgn == b"+" else -off))
h, m = divmod(val, 60)
try:
- return time(h, m, s, us, self._tz_from_sec(off))
+ return time(h, m, s, us, timezone(timedelta(seconds=-off)))
except ValueError:
raise DataError(
f"time not supported by Python: hour={h}"
) from None
- def _tz_from_sec(self, sec: int) -> timezone:
- return timezone(timedelta(seconds=-sec))
-
- def _tz_from_sec_36(self, sec: int) -> timezone:
- if sec % 60:
- sec = round(sec / 60.0) * 60
- return timezone(timedelta(seconds=-sec))
-
-
-if sys.version_info < (3, 7):
- setattr(
- TimetzBinaryLoader, "_tz_from_sec", TimetzBinaryLoader._tz_from_sec_36
- )
-
class TimestampLoader(Loader):
Operating System :: Microsoft :: Windows
Operating System :: POSIX
Programming Language :: Python :: 3
- Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
license_file = LICENSE.txt
[options]
-python_requires = >= 3.6
+python_requires = >= 3.7
packages = find:
zip_safe = False
install_requires =
backports.zoneinfo >= 0.2.0; python_version < "3.9"
- dataclasses >= 0.8; python_version < "3.7"
typing_extensions >= 3.10; python_version < "3.8"
[options.package_data]
[tox]
-envlist = {3.6,3.7,3.8,3.9,3.10}
+envlist = {3.7,3.8,3.9,3.10}
isolated_build = True
[testenv]
from cpython cimport datetime as cdt
from cpython.dict cimport PyDict_GetItem
from cpython.object cimport PyObject, PyObject_CallFunctionObjArgs
-from cpython.version cimport PY_VERSION_HEX
cdef extern from "Python.h":
const char *PyUnicode_AsUTF8AndSize(unicode obj, Py_ssize_t *size) except NULL
cdef extern from *:
"""
-/* Hack to compile on Python 3.6 */
-#if PY_VERSION_HEX < 0x03070000
-#define PyTimeZone_FromOffset(x) x
-#endif
-
/* Multipliers from fraction of seconds to microseconds */
static int _uspad[] = {0, 100000, 10000, 1000, 100, 10, 1};
"""
s = bytes(data).decode("utf8", "replace")
raise e.DataError(f"can't parse timetz {s!r}")
- # Python < 3.7 didn't support seconds in the timezones
- if PY_VERSION_HEX < 0x03070000:
- offsecs = round(offsecs / 60.0) * 60
-
tz = _timezone_from_seconds(offsecs)
try:
return cdt.time_new(vals[0], vals[1], vals[2], us, tz)
m = val % 60
h = <int>(val // 60)
- # Python < 3.7 didn't support seconds in the timezones
- if PY_VERSION_HEX >= 0x03070000:
- off = round(off / 60.0) * 60
-
tz = _timezone_from_seconds(-off)
try:
return cdt.time_new(h, m, s, us, tz)
return <object>ptr
delta = cdt.timedelta_new(0, sec, 0)
- if PY_VERSION_HEX >= 0x03070000:
- tz = PyTimeZone_FromOffset(delta)
- else:
- tz = timezone(delta)
+ tz = timezone(delta)
__cache[pysec] = tz
return tz
sname = tzname.decode() if tzname else "UTC"
try:
zi = ZoneInfo(sname)
- # Usually KeyError, but might be a DeprecationWarning raised by -Werror
- # (experienced on Python 3.6.12, backport.zoneinfo 0.2.1).
- # https://github.com/pganssle/zoneinfo/issues/109
- # Curiously, not trapping the latter, causes a segfault.
- # In such case the error message is wrong, but hey.
- except Exception:
+ except KeyError:
logger = logging.getLogger("psycopg")
logger.warning(
"unknown PostgreSQL timezone: %r; will use UTC", sname
Operating System :: Microsoft :: Windows
Operating System :: POSIX
Programming Language :: Python :: 3
- Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
license_file = LICENSE.txt
[options]
-python_requires = >= 3.6
+python_requires = >= 3.7
setup_requires = Cython >= 3.0a5
packages = find:
zip_safe = False
[tox]
-envlist = {3.6,3.7,3.8,3.9,3.10}
+envlist = {3.7,3.8,3.9,3.10}
isolated_build = True
[testenv]
T = TypeVar("T")
FutureT = Union["asyncio.Future[T]", Generator[Any, None, T], Awaitable[T]]
-if sys.version_info >= (3, 7):
- from contextlib import asynccontextmanager
-else:
-
- def asynccontextmanager(func):
- def helper(*args, **kwds):
- raise NotImplementedError(
- "async pool not implemented on Python 3.6"
- )
-
- return helper
-
-
if sys.version_info >= (3, 8):
create_task = asyncio.create_task
Task = asyncio.Task
-elif sys.version_info >= (3, 7):
-
- def create_task(
- coro: FutureT[T], name: Optional[str] = None
- ) -> "asyncio.Future[T]":
- return asyncio.create_task(coro)
-
- Task = asyncio.Future
-
else:
def create_task(
coro: FutureT[T], name: Optional[str] = None
) -> "asyncio.Future[T]":
- return asyncio.ensure_future(coro)
+ return asyncio.create_task(coro)
Task = asyncio.Future
"Counter",
"Deque",
"Task",
- "asynccontextmanager",
"create_task",
]
# Copyright (C) 2021 The Psycopg Team
-import sys
import asyncio
import logging
from abc import ABC, abstractmethod
from typing import Any, AsyncIterator, Awaitable, Callable
from typing import Dict, List, Optional, Sequence, Type
from weakref import ref
+from contextlib import asynccontextmanager
from psycopg import errors as e
from psycopg.pq import TransactionStatus
from .base import ConnectionAttempt, BasePool
from .sched import AsyncScheduler
from .errors import PoolClosed, PoolTimeout, TooManyRequests
-from ._compat import Task, asynccontextmanager, create_task, Deque
+from ._compat import Task, create_task, Deque
logger = logging.getLogger("psycopg.pool")
] = None,
**kwargs: Any,
):
- # https://bugs.python.org/issue42600
- if sys.version_info < (3, 7):
- raise e.NotSupportedError(
- "async pool not supported before Python 3.7"
- )
-
self.connection_class = connection_class
self._configure = configure
self._reset = reset
Operating System :: Microsoft :: Windows
Operating System :: POSIX
Programming Language :: Python :: 3
- Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
license_file = LICENSE.txt
[options]
-python_requires = >= 3.6
+python_requires = >= 3.7
packages = find:
zip_safe = False
# Maybe it can be useful after release, now it gets in the way.
[tool.pytest.ini_options]
filterwarnings = [
"error",
- # The zoneinfo warning ignore is only required on Python 3.6
- "ignore::DeprecationWarning:backports.zoneinfo._common",
]
testpaths=[
"tests",
loop = None
if sys.platform == "win32":
- if sys.version_info < (3, 7):
- loop = asyncio.SelectorEventLoop()
- asyncio.set_event_loop(loop)
- else:
- asyncio.set_event_loop_policy(
- asyncio.WindowsSelectorEventLoopPolicy()
- )
+ asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
if not loop:
loop = asyncio.get_event_loop_policy().new_event_loop()
yield loop
from math import isnan
from uuid import UUID
from random import choice, random, randrange
-from decimal import Decimal
-from contextlib import contextmanager
from typing import Any, List, Set, Tuple, Union
+from decimal import Decimal
+from contextlib import contextmanager, asynccontextmanager
import pytest
import psycopg
from psycopg import sql
from psycopg.adapt import PyFormat
-from psycopg._compat import asynccontextmanager, Deque
+from psycopg._compat import Deque
from psycopg.types.range import Range
from psycopg.types.numeric import Int4, Int8
from psycopg.types.multirange import Multirange
-import sys
import logging
import weakref
from time import sleep, time
assert conn.info.transaction_status == TransactionStatus.IDLE
-@pytest.mark.skipif(
- sys.version_info >= (3, 7), reason="async pool supported from Python 3.7"
-)
-def test_async_pool_not_supported(dsn):
- # note: this test is here because the all the ones in test_pool_async are
- # skipped on Py 3.6
- with pytest.raises(psycopg.NotSupportedError):
- pool.AsyncConnectionPool(dsn)
-
-
@pytest.mark.slow
@pytest.mark.timing
def test_stats_measures(dsn):
-import sys
import asyncio
import logging
from time import time
# Tests should have been skipped if the package is not available
pass
-pytestmark = [
- pytest.mark.asyncio,
- pytest.mark.skipif(
- sys.version_info < (3, 7),
- reason="async pool not supported before Python 3.7",
- ),
-]
+pytestmark = [pytest.mark.asyncio]
async def test_defaults(dsn):
import os
-import sys
import pytest
@pytest.mark.parametrize(
"filename",
- [
- "adapters_example.py",
- pytest.param(
- "typing_example.py",
- marks=pytest.mark.skipif(
- sys.version_info < (3, 7), reason="no future annotations"
- ),
- ),
- ],
+ ["adapters_example.py", "typing_example.py"],
)
def test_typing_example(mypy, filename):
cp = mypy.run_on_file(os.path.join(HERE, filename))
assert rec[0] == want
assert rec[1] == 11111111
- tz_sec = pytest.mark.skipif(
- sys.version_info < (3, 7), reason="no seconds in tz offset"
- )
-
@pytest.mark.xfail(sys.platform == "win32", reason="no IANA db on Windows")
@pytest.mark.parametrize(
"valname, tzval, tzname",
[
("max", "-06", "America/Chicago"),
- pytest.param("min", "+09:18:59", "Asia/Tokyo", marks=[tz_sec]),
+ ("min", "+09:18:59", "Asia/Tokyo"),
],
)
@pytest.mark.parametrize("fmt_out", pq.Format)
import sys
import psycopg
-# In 3.6 it seems already loaded (at least on Travis).
-if sys.version_info >= (3, 7):
- assert 'ipaddress' not in sys.modules
+assert 'ipaddress' not in sys.modules
conn = psycopg.connect({dsn!r})
with conn.cursor() as cur: