diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 61b13a7..541d63e 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -61,9 +61,20 @@ services: POSTGRES_DB: postgres POSTGRES_PASSWORD: postgres + pg17: + image: postgres:17 + restart: unless-stopped + volumes: + - postgres-data-17:/var/lib/postgresql/data + environment: + POSTGRES_USER: postgres + POSTGRES_DB: postgres + POSTGRES_PASSWORD: postgres + volumes: postgres-data-10: postgres-data-12: postgres-data-14: postgres-data-15: postgres-data-16: + postgres-data-17: diff --git a/.devcontainer/provision.sh b/.devcontainer/provision.sh index 5515b68..1ca7b02 100644 --- a/.devcontainer/provision.sh +++ b/.devcontainer/provision.sh @@ -27,6 +27,7 @@ apt-get install -y python3.9 python3.9-dev python3.9-distutils apt-get install -y python3.10 python3.10-dev python3.10-distutils apt-get install -y python3.11 python3.11-dev python3.11-distutils apt-get install -y python3.12 python3.12-dev python3.12-distutils +apt-get install -y python3.13 python3.13-dev python3.13-distutils # install build and testing tool @@ -38,6 +39,7 @@ python3.9 -m pip install -U pip setuptools wheel build python3.10 -m pip install -U pip setuptools wheel build python3.11 -m pip install -U pip setuptools wheel build python3.12 -m pip install -U pip setuptools wheel build +python3.13 -m pip install -U pip setuptools wheel build pip install ruff @@ -48,7 +50,7 @@ pip install -U tox apt-get install -y postgresql libpq-dev -for pghost in pg10 pg12 pg14 pg15 pg16 +for pghost in pg10 pg12 pg14 pg15 pg16 pg17 do export PGHOST=$pghost export PGDATABASE=postgres diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 50248b6..d88cd64 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -13,16 +13,16 @@ jobs: steps: - name: Check out repository uses: actions/checkout@v4 - - name: Set up Python 3.12 + - name: Set up Python 3.13 uses: actions/setup-python@v5 with: - python-version: 3.12 + python-version: 3.13 - name: Install dependencies run: | sudo apt install libpq-dev python -m pip install --upgrade pip pip install . - pip install "sphinx>=7,<8" + pip install "sphinx>=8,<9" - name: Create docs with Sphinx run: | cd docs diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 9e5c0bd..66d7909 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -20,7 +20,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v5 with: - python-version: 3.12 + python-version: 3.13 - name: Run quality checks run: tox -e ruff,mypy,cformat,docs timeout-minutes: 5 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 822fabd..920e3f3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -21,14 +21,16 @@ jobs: - { python: "3.10", postgres: "14" } - { python: "3.11", postgres: "15" } - { python: "3.12", postgres: "16" } + - { python: "3.13", postgres: "17" } # Opposite extremes of the supported Py/PG range, other architecture - - { python: "3.7", postgres: "16", architecture: "x86" } - - { python: "3.8", postgres: "15", architecture: "x86" } - - { python: "3.9", postgres: "14", architecture: "x86" } - - { python: "3.10", postgres: "13", architecture: "x86" } - - { python: "3.11", postgres: "12", architecture: "x86" } - - { python: "3.12", postgres: "11", architecture: "x86" } + - { python: "3.7", postgres: "17", architecture: "x86" } + - { python: "3.8", postgres: "16", architecture: "x86" } + - { python: "3.9", postgres: "15", architecture: "x86" } + - { python: "3.10", postgres: "14", architecture: "x86" } + - { python: "3.11", postgres: "13", architecture: "x86" } + - { python: "3.12", postgres: "12", architecture: "x86" } + - { python: "3.13", postgres: "11", architecture: "x86" } env: PYGRESQL_DB: test diff --git a/LICENSE.txt b/LICENSE.txt index b34bf23..e905706 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -6,7 +6,7 @@ Copyright (c) 1995, Pascal Andre Further modifications copyright (c) 1997-2008 by D'Arcy J.M. Cain -Further modifications copyright (c) 2009-2024 by the PyGreSQL Development Team +Further modifications copyright (c) 2009-2025 by the PyGreSQL Development Team PyGreSQL is released under the PostgreSQL License, a liberal Open Source license, similar to the BSD or MIT licenses: diff --git a/README.rst b/README.rst index e9f9465..46a09c2 100644 --- a/README.rst +++ b/README.rst @@ -18,8 +18,8 @@ The following Python versions are supported: * PyGreSQL 5.x: Python 2 and Python 3 * PyGreSQL 6.x and newer: Python 3 only -The current version of PyGreSQL supports Python versions 3.7 to 3.12 -and PostgreSQL versions 10 to 16 on the server. +The current version of PyGreSQL supports Python versions 3.7 to 3.13 +and PostgreSQL versions 10 to 17 on the server. Installation ------------ diff --git a/docs/about.rst b/docs/about.rst index 180af45..10ceaf5 100644 --- a/docs/about.rst +++ b/docs/about.rst @@ -8,7 +8,7 @@ powerful PostgreSQL features from Python. | This software is copyright © 1995, Pascal Andre. | Further modifications are copyright © 1997-2008 by D'Arcy J.M. Cain. - | Further modifications are copyright © 2009-2024 by the PyGreSQL team. + | Further modifications are copyright © 2009-2025 by the PyGreSQL team. | For licensing details, see the full :doc:`copyright`. **PostgreSQL** is a highly scalable, SQL compliant, open source @@ -39,6 +39,6 @@ on the PyGres95 code written by Pascal Andre (andre@chimay.via.ecp.fr). D'Arcy (darcy@druid.net) renamed it to PyGreSQL starting with version 2.0 and serves as the "BDFL" of PyGreSQL. -The current version PyGreSQL |version| needs PostgreSQL 10 to 16, and Python -3.7 to 3.12. If you need to support older PostgreSQL or Python versions, +The current version PyGreSQL |version| needs PostgreSQL 10 to 17, and Python +3.7 to 3.13. If you need to support older PostgreSQL or Python versions, you can resort to the PyGreSQL 5.x versions that still support them. diff --git a/docs/conf.py b/docs/conf.py index 45a86cd..f25d78e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -8,7 +8,7 @@ project = 'PyGreSQL' author = 'The PyGreSQL team' -copyright = '2024, ' + author +copyright = '2025, ' + author def project_version(): with open('../pyproject.toml') as f: diff --git a/docs/contents/changelog.rst b/docs/contents/changelog.rst index 6afcb1e..ad5f7f0 100644 --- a/docs/contents/changelog.rst +++ b/docs/contents/changelog.rst @@ -1,6 +1,10 @@ ChangeLog ========= +Version 6.1.0 (2024-12-05) +-------------------------- +- Support Python 3.13 and PostgreSQL 17. + Version 6.0.1 (2024-04-19) -------------------------- - Properly adapt falsy JSON values (#86) diff --git a/docs/contents/install.rst b/docs/contents/install.rst index 7d28ea5..2369452 100644 --- a/docs/contents/install.rst +++ b/docs/contents/install.rst @@ -14,7 +14,7 @@ On Windows, you also need to make sure that the directory that contains ``libpq.dll`` is part of your ``PATH`` environment variable. The current version of PyGreSQL has been tested with Python versions -3.7 to 3.12, and PostgreSQL versions 10 to 16. +3.7 to 3.13, and PostgreSQL versions 10 to 17. PyGreSQL will be installed as two packages named ``pg`` (for the classic interface) and ``pgdb`` (for the DB API 2 compliant interface). The former diff --git a/docs/contents/pg/db_wrapper.rst b/docs/contents/pg/db_wrapper.rst index 1dbd18e..b9e72b6 100644 --- a/docs/contents/pg/db_wrapper.rst +++ b/docs/contents/pg/db_wrapper.rst @@ -715,7 +715,7 @@ delete -- delete a row from a database table Delete a row from a database table :param str table: name of table - :param dict d: optional dictionary of values + :param dict row: optional dictionary of values :param col: optional keyword arguments for updating the dictionary :rtype: None :raises pg.ProgrammingError: table has no primary key, diff --git a/docs/copyright.rst b/docs/copyright.rst index 60739ef..bf7d9b0 100644 --- a/docs/copyright.rst +++ b/docs/copyright.rst @@ -10,7 +10,7 @@ Copyright (c) 1995, Pascal Andre Further modifications copyright (c) 1997-2008 by D'Arcy J.M. Cain (darcy@PyGreSQL.org) -Further modifications copyright (c) 2009-2024 by the PyGreSQL team. +Further modifications copyright (c) 2009-2025 by the PyGreSQL team. Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement diff --git a/ext/pgconn.c b/ext/pgconn.c index ddc958e..783eaff 100644 --- a/ext/pgconn.c +++ b/ext/pgconn.c @@ -3,7 +3,7 @@ * * The connection object - this file is part a of the C extension module. * - * Copyright (c) 2024 by the PyGreSQL Development Team + * Copyright (c) 2025 by the PyGreSQL Development Team * * Please see the LICENSE.TXT file for specific restrictions. */ diff --git a/ext/pginternal.c b/ext/pginternal.c index 9b3952c..2529095 100644 --- a/ext/pginternal.c +++ b/ext/pginternal.c @@ -3,7 +3,7 @@ * * Internal functions - this file is part a of the C extension module. * - * Copyright (c) 2024 by the PyGreSQL Development Team + * Copyright (c) 2025 by the PyGreSQL Development Team * * Please see the LICENSE.TXT file for specific restrictions. */ diff --git a/ext/pglarge.c b/ext/pglarge.c index f19568c..1b817b2 100644 --- a/ext/pglarge.c +++ b/ext/pglarge.c @@ -3,7 +3,7 @@ * * Large object support - this file is part a of the C extension module. * - * Copyright (c) 2024 by the PyGreSQL Development Team + * Copyright (c) 2025 by the PyGreSQL Development Team * * Please see the LICENSE.TXT file for specific restrictions. */ diff --git a/ext/pgmodule.c b/ext/pgmodule.c index 26b916d..916adda 100644 --- a/ext/pgmodule.c +++ b/ext/pgmodule.c @@ -3,7 +3,7 @@ * * This is the main file for the C extension module. * - * Copyright (c) 2024 by the PyGreSQL Development Team + * Copyright (c) 2025 by the PyGreSQL Development Team * * Please see the LICENSE.TXT file for specific restrictions. */ diff --git a/ext/pgnotice.c b/ext/pgnotice.c index ca051d8..c56b249 100644 --- a/ext/pgnotice.c +++ b/ext/pgnotice.c @@ -3,7 +3,7 @@ * * The notice object - this file is part a of the C extension module. * - * Copyright (c) 2024 by the PyGreSQL Development Team + * Copyright (c) 2025 by the PyGreSQL Development Team * * Please see the LICENSE.TXT file for specific restrictions. */ diff --git a/ext/pgquery.c b/ext/pgquery.c index fe5dda4..b87eba1 100644 --- a/ext/pgquery.c +++ b/ext/pgquery.c @@ -3,7 +3,7 @@ * * The query object - this file is part a of the C extension module. * - * Copyright (c) 2024 by the PyGreSQL Development Team + * Copyright (c) 2025 by the PyGreSQL Development Team * * Please see the LICENSE.TXT file for specific restrictions. */ diff --git a/ext/pgsource.c b/ext/pgsource.c index 4e19757..bbec2f8 100644 --- a/ext/pgsource.c +++ b/ext/pgsource.c @@ -3,7 +3,7 @@ * * The source object - this file is part a of the C extension module. * - * Copyright (c) 2024 by the PyGreSQL Development Team + * Copyright (c) 2025 by the PyGreSQL Development Team * * Please see the LICENSE.TXT file for specific restrictions. */ diff --git a/pg/__init__.py b/pg/__init__.py index cb4c7c3..c3b7f4e 100644 --- a/pg/__init__.py +++ b/pg/__init__.py @@ -4,7 +4,7 @@ # # This file contains the classic pg module. # -# Copyright (c) 2024 by the PyGreSQL Development Team +# Copyright (c) 2025 by the PyGreSQL Development Team # # The notification handler is based on pgnotify which is # Copyright (c) 2001 Ng Pheng Siong. All rights reserved. @@ -99,34 +99,86 @@ from .notify import NotificationHandler __all__ = [ - 'DB', 'Adapter', - 'NotificationHandler', 'Typecasts', - 'Bytea', 'Hstore', 'Json', 'Literal', - 'Error', 'Warning', - 'DataError', 'DatabaseError', - 'IntegrityError', 'InterfaceError', 'InternalError', - 'InvalidResultError', 'MultipleResultsError', - 'NoResultError', 'NotSupportedError', - 'OperationalError', 'ProgrammingError', - 'Connection', 'Query', 'RowCache', - 'INV_READ', 'INV_WRITE', - 'POLLING_OK', 'POLLING_FAILED', 'POLLING_READING', 'POLLING_WRITING', - 'RESULT_DDL', 'RESULT_DML', 'RESULT_DQL', 'RESULT_EMPTY', - 'SEEK_CUR', 'SEEK_END', 'SEEK_SET', - 'TRANS_ACTIVE', 'TRANS_IDLE', 'TRANS_INERROR', - 'TRANS_INTRANS', 'TRANS_UNKNOWN', - 'cast_array', 'cast_hstore', 'cast_record', - 'connect', 'escape_bytea', 'escape_string', 'unescape_bytea', - 'get_array', 'get_bool', 'get_bytea_escaped', - 'get_datestyle', 'get_decimal', 'get_decimal_point', - 'get_defbase', 'get_defhost', 'get_defopt', 'get_defport', 'get_defuser', - 'get_jsondecode', 'get_pqlib_version', 'get_typecast', - 'set_array', 'set_bool', 'set_bytea_escaped', - 'set_datestyle', 'set_decimal', 'set_decimal_point', - 'set_defbase', 'set_defhost', 'set_defopt', - 'set_defpasswd', 'set_defport', 'set_defuser', - 'set_jsondecode', 'set_query_helpers', 'set_typecast', - 'version', '__version__', + 'DB', + 'INV_READ', + 'INV_WRITE', + 'POLLING_FAILED', + 'POLLING_OK', + 'POLLING_READING', + 'POLLING_WRITING', + 'RESULT_DDL', + 'RESULT_DML', + 'RESULT_DQL', + 'RESULT_EMPTY', + 'SEEK_CUR', + 'SEEK_END', + 'SEEK_SET', + 'TRANS_ACTIVE', + 'TRANS_IDLE', + 'TRANS_INERROR', + 'TRANS_INTRANS', + 'TRANS_UNKNOWN', + 'Adapter', + 'Bytea', + 'Connection', + 'DataError', + 'DatabaseError', + 'Error', + 'Hstore', + 'IntegrityError', + 'InterfaceError', + 'InternalError', + 'InvalidResultError', + 'Json', + 'Literal', + 'MultipleResultsError', + 'NoResultError', + 'NotSupportedError', + 'NotificationHandler', + 'OperationalError', + 'ProgrammingError', + 'Query', + 'RowCache', + 'Typecasts', + 'Warning', + '__version__', + 'cast_array', + 'cast_hstore', + 'cast_record', + 'connect', + 'escape_bytea', + 'escape_string', + 'get_array', + 'get_bool', + 'get_bytea_escaped', + 'get_datestyle', + 'get_decimal', + 'get_decimal_point', + 'get_defbase', + 'get_defhost', + 'get_defopt', + 'get_defport', + 'get_defuser', + 'get_jsondecode', + 'get_pqlib_version', + 'get_typecast', + 'set_array', + 'set_bool', + 'set_bytea_escaped', + 'set_datestyle', + 'set_decimal', + 'set_decimal_point', + 'set_defbase', + 'set_defhost', + 'set_defopt', + 'set_defpasswd', + 'set_defport', + 'set_defuser', + 'set_jsondecode', + 'set_query_helpers', + 'set_typecast', + 'unescape_bytea', + 'version', ] __version__ = version diff --git a/pg/adapt.py b/pg/adapt.py index 2a5efaa..97e0391 100644 --- a/pg/adapt.py +++ b/pg/adapt.py @@ -21,8 +21,14 @@ from .db import DB __all__ = [ - 'Adapter', 'Bytea', 'DbType', 'DbTypes', - 'Hstore', 'Literal', 'Json', 'UUID' + 'UUID', + 'Adapter', + 'Bytea', + 'DbType', + 'DbTypes', + 'Hstore', + 'Json', + 'Literal' ] diff --git a/pg/cast.py b/pg/cast.py index ad1758b..98baa8f 100644 --- a/pg/cast.py +++ b/pg/cast.py @@ -25,10 +25,20 @@ from .tz import timezone_as_offset __all__ = [ - 'cast_bool', 'cast_json', 'cast_num', 'cast_money', 'cast_int2vector', - 'cast_date', 'cast_time', 'cast_timetz', 'cast_interval', - 'cast_timestamp','cast_timestamptz', - 'Typecasts', 'get_typecast', 'set_typecast' + 'Typecasts', + 'cast_bool', + 'cast_date', + 'cast_int2vector', + 'cast_interval', + 'cast_json', + 'cast_money', + 'cast_num', + 'cast_time', + 'cast_timestamp', + 'cast_timestamptz', + 'cast_timetz', + 'get_typecast', + 'set_typecast' ] def get_args(func: Callable) -> list: diff --git a/pg/core.py b/pg/core.py index e20bdbd..4d0c03c 100644 --- a/pg/core.py +++ b/pg/core.py @@ -108,29 +108,73 @@ ) __all__ = [ - 'Error', 'Warning', - 'DataError', 'DatabaseError', - 'IntegrityError', 'InterfaceError', 'InternalError', - 'InvalidResultError', 'MultipleResultsError', - 'NoResultError', 'NotSupportedError', - 'OperationalError', 'ProgrammingError', - 'Connection', 'Query', 'LargeObject', - 'INV_READ', 'INV_WRITE', - 'POLLING_OK', 'POLLING_FAILED', 'POLLING_READING', 'POLLING_WRITING', - 'RESULT_DDL', 'RESULT_DML', 'RESULT_DQL', 'RESULT_EMPTY', - 'SEEK_CUR', 'SEEK_END', 'SEEK_SET', - 'TRANS_ACTIVE', 'TRANS_IDLE', 'TRANS_INERROR', - 'TRANS_INTRANS', 'TRANS_UNKNOWN', - 'cast_array', 'cast_hstore', 'cast_record', - 'connect', 'escape_bytea', 'escape_string', 'unescape_bytea', - 'get_array', 'get_bool', 'get_bytea_escaped', - 'get_datestyle', 'get_decimal', 'get_decimal_point', - 'get_defbase', 'get_defhost', 'get_defopt', 'get_defport', 'get_defuser', - 'get_jsondecode', 'get_pqlib_version', - 'set_array', 'set_bool', 'set_bytea_escaped', - 'set_datestyle', 'set_decimal', 'set_decimal_point', - 'set_defbase', 'set_defhost', 'set_defopt', - 'set_defpasswd', 'set_defport', 'set_defuser', - 'set_jsondecode', 'set_query_helpers', + 'INV_READ', + 'INV_WRITE', + 'POLLING_FAILED', + 'POLLING_OK', + 'POLLING_READING', + 'POLLING_WRITING', + 'RESULT_DDL', + 'RESULT_DML', + 'RESULT_DQL', + 'RESULT_EMPTY', + 'SEEK_CUR', + 'SEEK_END', + 'SEEK_SET', + 'TRANS_ACTIVE', + 'TRANS_IDLE', + 'TRANS_INERROR', + 'TRANS_INTRANS', + 'TRANS_UNKNOWN', + 'Connection', + 'DataError', + 'DatabaseError', + 'Error', + 'IntegrityError', + 'InterfaceError', + 'InternalError', + 'InvalidResultError', + 'LargeObject', + 'MultipleResultsError', + 'NoResultError', + 'NotSupportedError', + 'OperationalError', + 'ProgrammingError', + 'Query', + 'Warning', + 'cast_array', + 'cast_hstore', + 'cast_record', + 'connect', + 'escape_bytea', + 'escape_string', + 'get_array', + 'get_bool', + 'get_bytea_escaped', + 'get_datestyle', + 'get_decimal', + 'get_decimal_point', + 'get_defbase', + 'get_defhost', + 'get_defopt', + 'get_defport', + 'get_defuser', + 'get_jsondecode', + 'get_pqlib_version', + 'set_array', + 'set_bool', + 'set_bytea_escaped', + 'set_datestyle', + 'set_decimal', + 'set_decimal_point', + 'set_defbase', + 'set_defhost', + 'set_defopt', + 'set_defpasswd', + 'set_defport', + 'set_defuser', + 'set_jsondecode', + 'set_query_helpers', + 'unescape_bytea', 'version', ] diff --git a/pg/error.py b/pg/error.py index 484a125..f4b9fd0 100644 --- a/pg/error.py +++ b/pg/error.py @@ -14,7 +14,12 @@ ) __all__ = [ - 'error', 'db_error', 'if_error', 'int_error', 'op_error', 'prg_error' + 'db_error', + 'error', + 'if_error', + 'int_error', + 'op_error', + 'prg_error' ] # Error messages diff --git a/pg/helpers.py b/pg/helpers.py index 53689f6..9d17674 100644 --- a/pg/helpers.py +++ b/pg/helpers.py @@ -13,8 +13,14 @@ SomeNamedTuple = Any # alias for accessing arbitrary named tuples __all__ = [ - 'quote_if_unqualified', 'oid_key', 'QuoteDict', 'RowCache', - 'dictiter', 'namediter', 'namednext', 'scalariter' + 'QuoteDict', + 'RowCache', + 'dictiter', + 'namediter', + 'namednext', + 'oid_key', + 'quote_if_unqualified', + 'scalariter' ] diff --git a/pgdb/__init__.py b/pgdb/__init__.py index 2604074..132ce29 100644 --- a/pgdb/__init__.py +++ b/pgdb/__init__.py @@ -4,7 +4,7 @@ # # This file contains the DB-API 2 compatible pgdb module. # -# Copyright (c) 2024 by the PyGreSQL Development Team +# Copyright (c) 2025 by the PyGreSQL Development Team # # Please see the LICENSE.TXT file for specific restrictions. @@ -121,21 +121,62 @@ from .cursor import Cursor __all__ = [ - 'Connection', 'Cursor', - 'Date', 'Time', 'Timestamp', - 'DateFromTicks', 'TimeFromTicks', 'TimestampFromTicks', - 'Binary', 'Interval', 'Uuid', - 'Hstore', 'Json', 'Literal', 'DbType', - 'STRING', 'BINARY', 'NUMBER', 'DATETIME', 'ROWID', 'BOOL', - 'SMALLINT', 'INTEGER', 'LONG', 'FLOAT', 'NUMERIC', 'MONEY', - 'DATE', 'TIME', 'TIMESTAMP', 'INTERVAL', - 'UUID', 'HSTORE', 'JSON', 'ARRAY', 'RECORD', - 'Error', 'Warning', - 'InterfaceError', 'DatabaseError', 'DataError', 'OperationalError', - 'IntegrityError', 'InternalError', 'ProgrammingError', 'NotSupportedError', - 'get_typecast', 'set_typecast', 'reset_typecast', - 'apilevel', 'connect', 'paramstyle', 'shortcutmethods', 'threadsafety', - 'version', '__version__', + 'ARRAY', + 'BINARY', + 'BOOL', + 'DATE', + 'DATETIME', + 'FLOAT', + 'HSTORE', + 'INTEGER', + 'INTERVAL', + 'JSON', + 'LONG', + 'MONEY', + 'NUMBER', + 'NUMERIC', + 'RECORD', + 'ROWID', + 'SMALLINT', + 'STRING', + 'TIME', + 'TIMESTAMP', + 'UUID', + 'Binary', + 'Connection', + 'Cursor', + 'DataError', + 'DatabaseError', + 'Date', + 'DateFromTicks', + 'DbType', + 'Error', + 'Hstore', + 'IntegrityError', + 'InterfaceError', + 'InternalError', + 'Interval', + 'Json', + 'Literal', + 'NotSupportedError', + 'OperationalError', + 'ProgrammingError', + 'Time', + 'TimeFromTicks', + 'Timestamp', + 'TimestampFromTicks', + 'Uuid', + 'Warning', + '__version__', + 'apilevel', + 'connect', + 'get_typecast', + 'paramstyle', + 'reset_typecast', + 'set_typecast', + 'shortcutmethods', + 'threadsafety', + 'version', ] __version__ = version diff --git a/pgdb/adapt.py b/pgdb/adapt.py index b89986b..f657b19 100644 --- a/pgdb/adapt.py +++ b/pgdb/adapt.py @@ -12,12 +12,36 @@ from .typecode import TypeCode __all__ = [ - 'DbType', 'ArrayType', 'RecordType', - 'STRING', 'BINARY', 'NUMBER', 'DATETIME', 'ROWID', 'BOOL', 'SMALLINT', - 'INTEGER', 'LONG', 'FLOAT', 'NUMERIC', 'MONEY', 'DATE', 'TIME', - 'TIMESTAMP', 'INTERVAL', 'UUID', 'HSTORE', 'JSON', 'ARRAY', 'RECORD', - 'Date', 'Time', 'Timestamp', - 'DateFromTicks', 'TimeFromTicks', 'TimestampFromTicks' + 'ARRAY', + 'BINARY', + 'BOOL', + 'DATE', + 'DATETIME', + 'FLOAT', + 'HSTORE', + 'INTEGER', + 'INTERVAL', + 'JSON', + 'LONG', + 'MONEY', + 'NUMBER', + 'NUMERIC', + 'RECORD', + 'ROWID', + 'SMALLINT', + 'STRING', + 'TIME', + 'TIMESTAMP', + 'UUID', + 'ArrayType', + 'Date', + 'DateFromTicks', + 'DbType', + 'RecordType', + 'Time', + 'TimeFromTicks', + 'Timestamp', + 'TimestampFromTicks' ] diff --git a/pgdb/cast.py b/pgdb/cast.py index 0336750..49b4bd8 100644 --- a/pgdb/cast.py +++ b/pgdb/cast.py @@ -24,11 +24,24 @@ from .typecode import TypeCode __all__ = [ - 'Decimal', 'decimal_type', 'cast_bool', 'cast_money', - 'cast_int2vector', 'cast_date', 'cast_time', 'cast_interval', - 'cast_timetz', 'cast_timestamp', 'cast_timestamptz', - 'get_typecast', 'set_typecast', 'reset_typecast', - 'Typecasts', 'LocalTypecasts', 'TypeCache', 'FieldInfo' + 'Decimal', + 'FieldInfo', + 'LocalTypecasts', + 'TypeCache', + 'Typecasts', + 'cast_bool', + 'cast_date', + 'cast_int2vector', + 'cast_interval', + 'cast_money', + 'cast_time', + 'cast_timestamp', + 'cast_timestamptz', + 'cast_timetz', + 'decimal_type', + 'get_typecast', + 'reset_typecast', + 'set_typecast' ] diff --git a/pyproject.toml b/pyproject.toml index ef1de2a..01b5086 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "PyGreSQL" -version = "6.0.1" +version = "6.1.0" requires-python = ">=3.7" authors = [ {name = "D'Arcy J. M. Cain", email = "darcy@pygresql.org"}, @@ -23,6 +23,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Programming Language :: SQL", "Topic :: Database", "Topic :: Database :: Front-Ends", @@ -78,7 +79,7 @@ ignore = ["D203", "D213"] "tests/*.py" = ["D100", "D101", "D102", "D103", "D105", "D107", "S"] [tool.mypy] -python_version = "3.12" +python_version = "3.13" check_untyped_defs = true no_implicit_optional = true strict_optional = true diff --git a/setup.py b/setup.py index 8b1ec5d..bf65227 100755 --- a/setup.py +++ b/setup.py @@ -136,7 +136,7 @@ def finalize_options(self): define_macros.append(('MS_WIN64', None)) elif compiler == 'msvc': # Microsoft Visual C++ extra_compile_args[1:] = [ - '-J', '-W3', '-WX', + '-J', '-W3', '-WX', '-wd4391', '-Dinline=__inline'] # needed for MSVC 9 @@ -170,6 +170,7 @@ def finalize_options(self): 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', 'Programming Language :: SQL', 'Topic :: Database', 'Topic :: Database :: Front-Ends', diff --git a/tests/test_classic_connection.py b/tests/test_classic_connection.py index 3f9427b..90d69a5 100755 --- a/tests/test_classic_connection.py +++ b/tests/test_classic_connection.py @@ -174,8 +174,8 @@ def test_attribute_protocol_version(self): def test_attribute_server_version(self): server_version = self.connection.server_version self.assertIsInstance(server_version, int) - self.assertGreaterEqual(server_version, 100000) - self.assertLess(server_version, 170000) + self.assertGreaterEqual(server_version, 100000) # >= 10.0 + self.assertLess(server_version, 190000) # < 20.0 def test_attribute_socket(self): socket = self.connection.socket @@ -984,7 +984,7 @@ def test_query_with_bool_params(self, bool_enabled=None): pg.set_bool(bool_enabled) try: bool_on = bool_enabled or bool_enabled is None - v_false, v_true = (False, True) if bool_on else 'ft' + v_false, v_true = (False, True) if bool_on else ('f', 't') r_false, r_true = [(v_false,)], [(v_true,)] self.assertEqual(query("select false").getresult(), r_false) self.assertEqual(query("select true").getresult(), r_true) @@ -1997,10 +1997,10 @@ def test_inserttable_byte_values(self): row_bytes = tuple( s.encode() if isinstance(s, str) else s for s in row_unicode) - data = [row_bytes] * 2 - self.c.inserttable('test', data) - data = [row_unicode] * 2 - self.assertEqual(self.get_back(), data) + data_bytes = [row_bytes] * 2 + self.c.inserttable('test', data_bytes) + data_unicode = [row_unicode] * 2 + self.assertEqual(self.get_back(), data_unicode) def test_inserttable_unicode_utf8(self): try: diff --git a/tests/test_classic_dbwrapper.py b/tests/test_classic_dbwrapper.py index f02955c..1d64c75 100755 --- a/tests/test_classic_dbwrapper.py +++ b/tests/test_classic_dbwrapper.py @@ -168,8 +168,8 @@ def test_attribute_protocol_version(self): def test_attribute_server_version(self): server_version = self.db.server_version self.assertIsInstance(server_version, int) - self.assertGreaterEqual(server_version, 100000) - self.assertLess(server_version, 170000) + self.assertGreaterEqual(server_version, 100000) # >= 10.0 + self.assertLess(server_version, 200000) # < 20.0 self.assertEqual(server_version, self.db.db.server_version) def test_attribute_socket(self): diff --git a/tests/test_classic_functions.py b/tests/test_classic_functions.py index 4351f79..d1bde01 100755 --- a/tests/test_classic_functions.py +++ b/tests/test_classic_functions.py @@ -124,8 +124,8 @@ def test_pqlib_version(self): # noinspection PyUnresolvedReferences v = pg.get_pqlib_version() self.assertIsInstance(v, int) - self.assertGreater(v, 100000) - self.assertLess(v, 170000) + self.assertGreater(v, 100000) # >= 10.0 + self.assertLess(v, 200000) # < 20.0 class TestParseArray(unittest.TestCase): diff --git a/tests/test_classic_largeobj.py b/tests/test_classic_largeobj.py index 4fb8773..7c53053 100755 --- a/tests/test_classic_largeobj.py +++ b/tests/test_classic_largeobj.py @@ -112,7 +112,7 @@ def test_lo_import(self): fname = 'temp_test_pg_largeobj_import.txt' f = open(fname, 'wb') # noqa: SIM115 else: - f = tempfile.NamedTemporaryFile() + f = tempfile.NamedTemporaryFile() # noqa: SIM115 fname = f.name data = b'some data to be imported' f.write(data) @@ -420,7 +420,7 @@ def test_export(self): fname = 'temp_test_pg_largeobj_export.txt' f = open(fname, 'wb') # noqa: SIM115 else: - f = tempfile.NamedTemporaryFile() + f = tempfile.NamedTemporaryFile() # noqa: SIM115 fname = f.name data = b'some data to be exported' self.obj.open(pg.INV_WRITE) @@ -441,12 +441,11 @@ def test_export(self): def test_export_in_existent(self): export = self.obj.export - f = tempfile.NamedTemporaryFile() - self.obj.open(pg.INV_WRITE) - self.obj.close() - self.pgcnx.query(f'select lo_unlink({self.obj.oid})') - self.assertRaises(IOError, export, f.name) - f.close() + with tempfile.NamedTemporaryFile() as f: + self.obj.open(pg.INV_WRITE) + self.obj.close() + self.pgcnx.query(f'select lo_unlink({self.obj.oid})') + self.assertRaises(IOError, export, f.name) if __name__ == '__main__': diff --git a/tests/test_dbapi20.py b/tests/test_dbapi20.py index ef4857d..0e70e07 100755 --- a/tests/test_dbapi20.py +++ b/tests/test_dbapi20.py @@ -161,7 +161,7 @@ def test_row_factory(self): class TestCursor(pgdb.Cursor): - def row_factory(self, row): + def row_factory(self, row): # type: ignore[override] description = self.description assert isinstance(description, list) return {f'column {desc[0]}': value diff --git a/tox.ini b/tox.ini index f703abb..2359c8d 100644 --- a/tox.ini +++ b/tox.ini @@ -1,36 +1,39 @@ # config file for tox [tox] -envlist = py3{7,8,9,10,11,12},ruff,mypy,cformat,docs +envlist = py3{7,8,9,10,11,12,13},ruff,mypy,cformat,docs +requires = # this is needed for compatibility with Python 3.7 + pip<24.1 + virtualenv<20.27 [testenv:ruff] -basepython = python3.12 -deps = ruff>=0.3.7 +basepython = python3.13 +deps = ruff>=0.8,<0.9 commands = ruff check setup.py pg pgdb tests [testenv:mypy] -basepython = python3.12 -deps = mypy>=1.9.0 +basepython = python3.13 +deps = mypy>=1.13,<1.14 commands = mypy pg pgdb tests [testenv:cformat] -basepython = python3.12 +basepython = python3.13 allowlist_externals = sh commands = sh -c "! (clang-format --style=file -n ext/*.c 2>&1 | tee /dev/tty | grep format-violations)" [testenv:docs] -basepython = python3.12 +basepython = python3.13 deps = - sphinx>=7,<8 + sphinx>=8,<9 commands = sphinx-build -b html -nEW docs docs/_build/html [testenv:build] -basepython = python3.12 +basepython = python3.13 deps = setuptools>=68 wheel>=0.42,<1 @@ -39,11 +42,11 @@ commands = python -m build -s -n -C strict -C memory-size [testenv:coverage] -basepython = python3.12 +basepython = python3.13 deps = coverage>=7,<8 commands = - coverage run -m unittest discover + coverage run -m unittest discover -v coverage html [testenv] @@ -54,4 +57,4 @@ deps = setuptools>=68 commands = python setup.py clean --all build_ext --force --inplace --strict --memory-size - python -m unittest {posargs:discover} + python -m unittest {posargs:discover -v}