diff --git a/LICENSE.txt b/LICENSE.txt index 298aadfb..5d3cd86e 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -7,7 +7,10 @@ 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-2017 by the PyGreSQL team. +Further modifications copyright (c) 2009-2019 by the PyGreSQL team. + +PyGreSQL is released under the PostgreSQL License, a liberal Open Source +license, similar to the BSD or MIT licenses: 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/MANIFEST.in b/MANIFEST.in index b2681522..083f4246 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -18,6 +18,8 @@ include docs/Makefile include docs/make.bat include docs/*.py include docs/*.rst +include docs/*.txt +exclude docs/index.rst recursive-include docs/community *.rst recursive-include docs/contents *.rst recursive-include docs/download *.rst diff --git a/docs/Makefile b/docs/Makefile index bbe5bf09..0a1113c9 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -3,7 +3,7 @@ # You can set these variables from the command line. SPHINXOPTS = -SPHINXBUILD = sphinx-build3.6 +SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build diff --git a/docs/about.txt b/docs/about.txt index 4baefe33..f4b8a57f 100644 --- a/docs/about.txt +++ b/docs/about.txt @@ -5,7 +5,7 @@ PostgreSQL features from a Python script. | This software is copyright © 1995, Pascal Andre. | Further modifications are copyright © 1997-2008 by D'Arcy J.M. Cain. - | Further modifications are copyright © 2009-2017 by the PyGreSQL team. + | Further modifications are copyright © 2009-2019 by the PyGreSQL team. | For licensing details, see the full :doc:`copyright`. **PostgreSQL** is a highly scalable, SQL compliant, open source @@ -36,6 +36,7 @@ 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 5.0.4 needs PostgreSQL 9.0 or newer and Python 2.6, -2.7 or 3.3 to 3.5. If you need to support older PostgreSQL versions or older -Python 2.x versions, you can resort to PyGreSQL 4.x that still supports them. +The current version PyGreSQL 5.0.7 needs PostgreSQL 9.0 to 9.6 or 10 or 11, and +Python 2.6, 2.7 or 3.3 to 3.7. If you need to support older PostgreSQL versions +or older Python 2.x versions, you can resort to the PyGreSQL 4.x versions that +still support them. diff --git a/docs/announce.rst b/docs/announce.rst index b4910878..8a0ca715 100644 --- a/docs/announce.rst +++ b/docs/announce.rst @@ -3,30 +3,27 @@ PyGreSQL Announcements ====================== --------------------------------- -Release of PyGreSQL version 5.0.4 +Release of PyGreSQL version 5.0.7 --------------------------------- -Release 5.0.4 of PyGreSQL. +Release 5.0.7 of PyGreSQL. -It is available at: http://pygresql.org/files/PyGreSQL-5.0.4.tar.gz. +It is available at: http://pygresql.org/files/PyGreSQL-5.0.7.tar.gz. If you are running NetBSD, look in the packages directory under databases. There is also a package in the FreeBSD ports collection. -Please refer to `changelog.txt `_ +Please refer to `changelog.txt `_ for things that have changed in this version. -Please refer to `readme.txt `_ -for general information. - This version has been built and unit tested on: - NetBSD - FreeBSD - openSUSE - Ubuntu - - Windows 7 with both MinGW and Visual Studio - - PostgreSQL 9.0 to 9.6 32 and 64bit - - Python 2.6, 2.7, 3.3, 3.4, 3.5, 3.6 32 and 64bit + - Windows 7 and 10 with both MinGW and Visual Studio + - PostgreSQL 9.0 to 9.6 and 10 or 11 (32 and 64bit) + - Python 2.6, 2.7 and 3.3 to 3.7 (32 and 64bit) | D'Arcy J.M. Cain | darcy@PyGreSQL.org diff --git a/docs/community/mailinglist.rst b/docs/community/mailinglist.rst index c0269512..b39a308a 100644 --- a/docs/community/mailinglist.rst +++ b/docs/community/mailinglist.rst @@ -2,7 +2,7 @@ Mailing list ------------ You can join -`the mailing list `_ +`the mailing list `_ to discuss future development of the PyGreSQL interface or if you have questions or problems with PyGreSQL that are not covered in the :doc:`documentation <../contents/index>`. diff --git a/docs/community/support.rst b/docs/community/support.rst index ac4fa6e8..457bd8bd 100644 --- a/docs/community/support.rst +++ b/docs/community/support.rst @@ -8,11 +8,11 @@ Support see http://www.postgresql.org/support/ **PyGreSQL**: - Join `the PyGreSQL mailing list `_ + Join `the PyGreSQL mailing list `_ if you need help regarding PyGreSQL. Please also send context diffs there, if you would like to proposes changes. Please note that messages to individual developers will generally not be answered directly. All questions, comments and code changes must be - submitted to the mailing list for peer review and archiving purposes. \ No newline at end of file + submitted to the mailing list for peer review and archiving purposes. diff --git a/docs/conf.py b/docs/conf.py index ff61ee54..89d99a12 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -61,7 +61,7 @@ # General information about the project. project = 'PyGreSQL' author = 'The PyGreSQL team' -copyright = '2017, ' + author +copyright = '2019, ' + author # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -70,7 +70,7 @@ # The short X.Y version. version = '5.0' # The full version, including alpha/beta/rc tags. -release = '5.0.4' +release = '5.0.7' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/contents/changelog.rst b/docs/contents/changelog.rst index 83f73c38..75f276de 100644 --- a/docs/contents/changelog.rst +++ b/docs/contents/changelog.rst @@ -1,8 +1,26 @@ ChangeLog ========= +Vesion 5.0.7 (2019-05-17) +------------------------- +- This version officially supports the new PostgreSQL 11. +- Fixed a bug in parsing array subscript ranges (reported by Justin Pryzby). +- Fixed an issue when deleting a DB wrapper object with the underlying + connection already closed (bug report by Jacob Champion). + +Vesion 5.0.6 (2018-07-29) +------------------------- +- This version officially supports the new Python 3.7. +- Correct trove classifier for the PostgreSQL License. + +Version 5.0.5 (2018-04-25) +-------------------------- +- This version officially supports the new PostgreSQL 10. +- The memory for the string with the number of rows affected by a classic pg + module query() was already freed (bug report and fix by Peifeng Qiu). + Version 5.0.4 (2017-07-23) ------------------------------- +-------------------------- - This version officially supports the new Python 3.6 and PostgreSQL 9.6. - query_formatted() can now be used without parameters. - The automatic renaming of columns that are invalid as field names of diff --git a/docs/contents/general.rst b/docs/contents/general.rst index c97ca7c7..51644b93 100644 --- a/docs/contents/general.rst +++ b/docs/contents/general.rst @@ -15,7 +15,7 @@ provides some higher-level and PostgreSQL specific convenience methods. .. seealso:: **DB-API 2.0** (Python Database API Specification v2.0) - is a specification for connecting to databases (not only PostGreSQL) + is a specification for connecting to databases (not only PostgreSQL) from Python that has been developed by the Python DB-SIG in 1999. The authoritative programming information for the DB-API is :pep:`0249`. diff --git a/docs/contents/install.rst b/docs/contents/install.rst index db1b3d0b..4f9dd403 100644 --- a/docs/contents/install.rst +++ b/docs/contents/install.rst @@ -11,7 +11,7 @@ are on Windows, make sure that the directory with libpq.dll is in your ``PATH`` environment variable. The current version of PyGreSQL has been tested with Python versions -2.6, 2.7, 3.3 to 3.6 and PostGreSQL version 9.0 to 9.6. +2.6, 2.7 and 3.3 to 3.7, and PostgreSQL versions 9.0 to 9.6 and 10 or 11. PyGreSQL will be installed as three modules, a dynamic module called _pg.pyd, and two pure Python wrapper modules called pg.py and pgdb.py. diff --git a/docs/contents/pg/adaptation.rst b/docs/contents/pg/adaptation.rst index a5a9b0a1..02f378ca 100644 --- a/docs/contents/pg/adaptation.rst +++ b/docs/contents/pg/adaptation.rst @@ -64,7 +64,7 @@ adaptation of parameters, since all of this is happening automatically behind the scenes. You only need to consider this issue when creating SQL commands manually and sending them to the database using the :meth:`DB.query` method. -Imagine you have created a user login form that stores the login name as +Imagine you have created a user login form that stores the login name as *login* and the password as *passwd* and you now want to get the user data for that user. You may be tempted to execute a query like this:: @@ -307,7 +307,7 @@ We can set a different typecast function for ``int4``, but it will not become effective, the C module continues to use its internal typecasting. However, we can add new typecast functions for the database types that are -not supported by the C modul. Fore example, we can create a typecast function +not supported by the C module. For example, we can create a typecast function that casts items of the composite PostgreSQL type used as example in the previous section to instances of the corresponding Python class. @@ -363,8 +363,8 @@ With PostgreSQL we can easily calculate that these two circles overlap:: True However, calculating the intersection points between the two circles using the -``#`` operator does not work (at least not as of PostgreSQL version 9.6). -So let' resort to SymPy to find out. To ease importing circles from +``#`` operator does not work (at least not as of PostgreSQL version 11). +So let's resort to SymPy to find out. To ease importing circles from PostgreSQL to SymPy, we create and register the following typecast function:: >>> from sympy import Point, Circle diff --git a/docs/contents/pg/connection.rst b/docs/contents/pg/connection.rst index 4c40e700..654c9333 100644 --- a/docs/contents/pg/connection.rst +++ b/docs/contents/pg/connection.rst @@ -76,7 +76,7 @@ reset -- reset the connection .. method:: Connection.reset() Reset the :mod:`pg` connection - + :rtype: None :raises TypeError: too many (any) arguments :raises TypeError: invalid connection @@ -101,7 +101,7 @@ close -- close the database connection .. method:: Connection.close() Close the :mod:`pg` connection - + :rtype: None :raises TypeError: too many (any) arguments @@ -241,7 +241,40 @@ values may contain string, integer, long or double (real) values. .. warning:: This method doesn't type check the fields according to the table definition; - it just look whether or not it knows how to handle such types. + it just looks whether or not it knows how to handle such types. + +get/set_cast_hook -- fallback typecast function +----------------------------------------------- + +.. method:: Connection.get_cast_hook() + + Get the function that handles all external typecasting + + :returns: the current external typecast function + :rtype: callable, None + :raises TypeError: too many (any) arguments + +This returns the callback function used by PyGreSQL to provide plug-in +Python typecast functions for the connection. + +.. versionadded:: 5.0 + +.. method:: Connection.set_cast_hook(func) + + Set a function that will handle all external typecasting + + :param func: the function to be used as a callback + :rtype: None + :raises TypeError: the specified notice receiver is not callable + +This methods allows setting a custom fallback function for providing +Python typecast functions for the connection to supplement the C +extension module. If you set this function to *None*, then only the typecast +functions implemented in the C extension module are enabled. You normally +would not want to change this. Instead, you can use :func:`get_typecast` and +:func:`set_typecast` to add or change the plug-in Python typecast functions. + +.. versionadded:: 5.0 get/set_notice_receiver -- custom notice receiver ------------------------------------------------- @@ -352,7 +385,7 @@ locreate -- create a large object in the database [LO] Create a large object in the database :param int mode: large object create mode - :returns: object handling the PostGreSQL large object + :returns: object handling the PostgreSQL large object :rtype: :class:`LargeObject` :raises TypeError: invalid connection, bad parameter type, or too many parameters :raises pg.OperationalError: creation error @@ -370,7 +403,7 @@ getlo -- build a large object from given oid [LO] Create a large object in the database :param int oid: OID of the existing large object - :returns: object handling the PostGreSQL large object + :returns: object handling the PostgreSQL large object :rtype: :class:`LargeObject` :raises TypeError: invalid connection, bad parameter type, or too many parameters :raises ValueError: bad OID value (0 is invalid_oid) @@ -386,7 +419,7 @@ loimport -- import a file to a large object [LO] Import a file to a large object :param str name: the name of the file to be imported - :returns: object handling the PostGreSQL large object + :returns: object handling the PostgreSQL large object :rtype: :class:`LargeObject` :raises TypeError: invalid connection, bad argument type, or too many arguments :raises pg.OperationalError: error during file import diff --git a/docs/contents/pg/db_wrapper.rst b/docs/contents/pg/db_wrapper.rst index efb82c62..cd0fea92 100644 --- a/docs/contents/pg/db_wrapper.rst +++ b/docs/contents/pg/db_wrapper.rst @@ -314,7 +314,7 @@ If *row* is a dictionary, then the value for the key is taken from it. Otherwise, the row must be a single value or a tuple of values corresponding to the passed *keyname* or primary key. The fetched row from the table will be returned as a new dictionary or used to replace -the existing values when row was passed as aa dictionary. +the existing values when row was passed as a dictionary. The OID is also put into the dictionary if the table has one, but in order to allow the caller to work with multiple tables, it is @@ -621,6 +621,8 @@ If you set the *scalar* option to *True*, then instead of the named tuples you will get the first items of these tuples. This is useful if the result has only one column anyway. +.. versionadded:: 5.0 + .. method:: DB.get_as_dict(table, [keyname], [what], [where], [order], [limit], [offset], [scalar]) Get a table as a dictionary @@ -657,6 +659,8 @@ using the order specified with the *order* parameter or the key column(s) if not specified. You can set *order* to *False* if you don't care about the ordering. In this case the returned dictionary will be an ordinary one. +.. versionadded:: 5.0 + escape_literal/identifier/string/bytea -- escape for SQL -------------------------------------------------------- @@ -775,7 +779,7 @@ JSON data is automatically decoded by PyGreSQL. If you don't want the data to be decoded, then you can cast ``json`` or ``jsonb`` columns to ``text`` in PostgreSQL or you can set the decoding function to *None* or a different function using :func:`pg.set_jsondecode`. By default this is the same as -the :func:`json.dumps` function from the standard library. +the :func:`json.loads` function from the standard library. .. versionadded:: 5.0 diff --git a/docs/contents/pg/module.rst b/docs/contents/pg/module.rst index 62d426df..15205ee8 100644 --- a/docs/contents/pg/module.rst +++ b/docs/contents/pg/module.rst @@ -46,15 +46,22 @@ connect -- Open a PostgreSQL connection This function opens a connection to a specified database on a given PostgreSQL server. You can use keywords here, as described in the Python tutorial. The names of the keywords are the name of the -parameters given in the syntax line. For a precise description +parameters given in the syntax line. The ``opt`` parameter can be used +to pass command-line options to the server. For a precise description of the parameters, please refer to the PostgreSQL user manual. +If you want to add additional parameters not specified here, you must +pass a connection string or a connection URI instead of the ``dbname`` +(as in ``con3`` and ``con4`` in the following example). + Example:: import pg - con1 = pg.connect('testdb', 'myhost', 5432, None, None, 'bob', None) - con2 = pg.connect(dbname='testdb', host='localhost', user='bob') + con1 = pg.connect('testdb', 'myhost', 5432, None, 'bob', None) + con2 = pg.connect(dbname='testdb', host='myhost', user='bob') + con3 = pg.connect('host=myhost user=bob dbname=testdb connect_timeout=10') + con4 = pg.connect('postgresql://bob@myhost/testdb?connect_timeout=10') get/set_defhost -- default server host [DV] ------------------------------------------- @@ -370,7 +377,7 @@ get/set_decimal_point -- decimal mark used for monetary values This function returns the decimal mark used by PyGreSQL to interpret PostgreSQL monetary values when converting them to decimal numbers. The default setting is ``'.'`` as a decimal point. This setting is not -adapted automatically to the locale used by PostGreSQL, but you can use +adapted automatically to the locale used by PostgreSQL, but you can use :func:`set_decimal()` to set a different decimal mark manually. A return value of ``None`` means monetary values are not interpreted as decimal numbers, but returned as strings including the formatting and currency. @@ -386,7 +393,7 @@ numbers, but returned as strings including the formatting and currency. This function can be used to specify the decimal mark used by PyGreSQL to interpret PostgreSQL monetary values. The default value is '.' as a decimal point. This value is not adapted automatically to the locale -used by PostGreSQL, so if you are dealing with a database set to a +used by PostgreSQL, so if you are dealing with a database set to a locale that uses a ``','`` instead of ``'.'`` as the decimal point, then you need to call ``set_decimal(',')`` to have PyGreSQL interpret monetary values correctly. If you don't want money values to be converted @@ -524,29 +531,6 @@ the automatic deserialization of JSON strings will be deactivated. .. versionchanged:: 5.0 JSON data had been always returned as text strings in earlier versions. -get/set_cast_hook -- fallback typecast function ------------------------------------------------ - -.. function:: get_cast_hook() - - Get the function that handles all external typecasting - -This returns the callback function used by PyGreSQL to provide plug-in -Python typecast functions. - -.. function:: set_cast_hook(func) - - Set a function that will handle all external typecasting - - :param func: the function to be used as a callback - -If you set this function to *None*, then only the typecast functions -implemented in the C extension module are enabled. You normally would -not want to change this. Instead, you can use :func:`get_typecast` and -:func:`set_typecast` to add or change the plug-in Python typecast functions. - -.. versionadded:: 5.0 - get/set_datestyle -- assume a fixed date style ---------------------------------------------- diff --git a/docs/contents/pg/query.rst b/docs/contents/pg/query.rst index 55e8b543..2be12073 100644 --- a/docs/contents/pg/query.rst +++ b/docs/contents/pg/query.rst @@ -42,7 +42,7 @@ dictresult -- get query values as list of dictionaries :raises MemoryError: internal memory error This method returns the list of the values returned by the query -with each tuple returned as a dictionary with the field names +with each row returned as a dictionary with the field names used as the dictionary index. Note that since PyGreSQL 5.0 this will return the values of array type diff --git a/docs/contents/pgdb/cursor.rst b/docs/contents/pgdb/cursor.rst index 307d7098..a2ac63e8 100644 --- a/docs/contents/pgdb/cursor.rst +++ b/docs/contents/pgdb/cursor.rst @@ -55,7 +55,7 @@ rowcount -- number of rows of the result :meth:`Cursor.execute` or :meth:`Cursor.executemany` call produced (for DQL statements like SELECT) or affected (for DML statements like UPDATE or INSERT). It is also set by the :meth:`Cursor.copy_from` and - :meth':`Cursor.copy_to` methods. The attribute is -1 in case no such + :meth:`Cursor.copy_to` methods. The attribute is -1 in case no such method call has been performed on the cursor or the rowcount of the last operation cannot be determined by the interface. @@ -117,7 +117,7 @@ executemany -- execute many similar database operations Prepare a database operation (query or command) and then execute it against all parameter tuples or mappings found in the sequence *seq_of_parameters*. -Parameters are bounded to the query using Python extended format codes, +Parameters are bound to the query using Python extended format codes, e.g. ``" ... WHERE name=%(name)s"``. callproc -- Call a stored procedure diff --git a/docs/contents/pgdb/introduction.rst b/docs/contents/pgdb/introduction.rst index 7c8bd42d..5eb4f0a8 100644 --- a/docs/contents/pgdb/introduction.rst +++ b/docs/contents/pgdb/introduction.rst @@ -8,7 +8,7 @@ provided by the :mod:`pgdb` module. The following part of the documentation covers only the newer :mod:`pgdb` API. **DB-API 2.0** (Python Database API Specification v2.0) -is a specification for connecting to databases (not only PostGreSQL) +is a specification for connecting to databases (not only PostgreSQL) from Python that has been developed by the Python DB-SIG in 1999. The authoritative programming information for the DB-API is :pep:`0249`. diff --git a/docs/contents/postgres/advanced.rst b/docs/contents/postgres/advanced.rst index 65d02ecd..38c8a473 100644 --- a/docs/contents/postgres/advanced.rst +++ b/docs/contents/postgres/advanced.rst @@ -11,7 +11,7 @@ database, as explained in the :doc:`basic`:: >>> from pg import DB >>> db = DB() - >>> query = query + >>> query = db.query Inheritance ----------- diff --git a/docs/contents/postgres/func.rst b/docs/contents/postgres/func.rst index c2d5dd63..5dba156a 100644 --- a/docs/contents/postgres/func.rst +++ b/docs/contents/postgres/func.rst @@ -22,7 +22,7 @@ Let's create a simple SQL function that takes no arguments and returns 1:: >>> query("""CREATE FUNCTION one() RETURNS int4 ... AS 'SELECT 1 as ONE' LANGUAGE SQL""") -Functions can be used in any expressions (eg. in the target"list or +Functions can be used in any expressions (eg. in the target list or qualifications):: >>> print(db.query("SELECT one() AS answer")) diff --git a/docs/contents/postgres/syscat.rst b/docs/contents/postgres/syscat.rst index 1fcca4fc..0c34c834 100644 --- a/docs/contents/postgres/syscat.rst +++ b/docs/contents/postgres/syscat.rst @@ -19,7 +19,8 @@ database, as explained in the :doc:`basic`:: >>> from pg import DB >>> db = DB() - >>> query = query + >>> query = db.query + Lists indices ------------- @@ -49,6 +50,7 @@ in user-defined classes:: AND NOT a.attisdropped ORDER BY relname, attname""")) + List user defined base types ---------------------------- @@ -62,8 +64,8 @@ This query lists all user defined base types:: ORDER BY rolname, typname""")) -List operators ---------------- +List operators +-------------- This query lists all right-unary operators:: @@ -121,6 +123,7 @@ they can be applied:: and p.proargtypes[0] = t.oid ORDER BY proname, typname""")) + List operator families ---------------------- diff --git a/docs/copyright.rst b/docs/copyright.rst index 16072476..52d4a22a 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-2017 by the PyGreSQL team. +Further modifications copyright (c) 2009-2019 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/docs/download/download.rst b/docs/download/download.rst index ac6df827..13f23cf7 100644 --- a/docs/download/download.rst +++ b/docs/download/download.rst @@ -17,11 +17,12 @@ A **FreeBSD package** is available in their ports collection An **openSUSE package** is available through their build service at * https://software.opensuse.org/package/PyGreSQL?search_term=pygresql A **Win32 installer** for various Python versions is available at - * http://pygresql.org/files/PyGreSQL-5.0.4.win-amd64-py2.6.exe - * http://pygresql.org/files/PyGreSQL-5.0.4.win-amd64-py2.7.exe - * http://pygresql.org/files/PyGreSQL-5.0.4.win-amd64-py3.4.exe - * http://pygresql.org/files/PyGreSQL-5.0.4.win-amd64-py3.5.exe - * http://pygresql.org/files/PyGreSQL-5.0.4.win-amd64-py3.6.exe + * http://pygresql.org/files/PyGreSQL-5.0.7.win-amd64-py2.6.exe + * http://pygresql.org/files/PyGreSQL-5.0.7.win-amd64-py2.7.exe + * http://pygresql.org/files/PyGreSQL-5.0.7.win-amd64-py3.4.exe + * http://pygresql.org/files/PyGreSQL-5.0.7.win-amd64-py3.5.exe + * http://pygresql.org/files/PyGreSQL-5.0.7.win-amd64-py3.6.exe + * http://pygresql.org/files/PyGreSQL-5.0.7.win-amd64-py3.7.exe Older PyGreSQL versions ----------------------- diff --git a/mktar b/mktar index 4a2c2b37..db4b050b 100755 --- a/mktar +++ b/mktar @@ -1,6 +1,6 @@ #! /bin/sh -VERSION=5.0.4 +VERSION=5.0.7 DISTDIR=/u/WEB/pyg/files # some safety tests diff --git a/pg.py b/pg.py index 8c7848a5..222ce2db 100644 --- a/pg.py +++ b/pg.py @@ -14,7 +14,7 @@ For a DB-API 2 compliant interface use the newer pgdb module. """ -# Copyright (c) 1997-2017 by D'Arcy J.M. Cain. +# Copyright (c) 1997-2019 by D'Arcy J.M. Cain. # # Contributions made by Ch. Zwerschke and others. # @@ -691,17 +691,14 @@ def format_query(self, command, values=None, types=None, inline=False): literals = [adapt(value) for value in values] else: add = params.add - literals = [] - append = literals.append if types: if (not isinstance(types, (list, tuple)) or len(types) != len(values)): raise TypeError('The values and types do not match') - for value, typ in zip(values, types): - append(add(value, typ)) + literals = [add(value, typ) + for value, typ in zip(values, types)] else: - for value in values: - append(add(value)) + literals = [add(value) for value in values] command %= tuple(literals) elif isinstance(values, dict): # we want to allow extra keys in the dictionary, @@ -722,15 +719,14 @@ def format_query(self, command, values=None, types=None, inline=False): for key, value in values.items()) else: add = params.add - literals = {} if types: if not isinstance(types, dict): raise TypeError('The values and types do not match') - for key in sorted(values): - literals[key] = add(values[key], types.get(key)) + literals = dict((key, add(values[key], types.get(key))) + for key in sorted(values)) else: - for key in sorted(values): - literals[key] = add(values[key]) + literals = dict((key, add(values[key])) + for key in sorted(values)) command %= literals else: raise TypeError('The values must be passed as tuple, list or dict') @@ -971,8 +967,8 @@ class Typecasts(dict): The cast functions get passed the string representation of a value in the database which they need to convert to a Python object. The - passed string will never be None since NULL values are already be - handled before the cast function is called. + passed string will never be None since NULL values are already handled + before the cast function is called. Note that the basic types are already handled by the C extension. They only need to be handled here as record or array components. @@ -1472,7 +1468,7 @@ def pgnotify(*args, **kw): return NotificationHandler(*args, **kw) -# The actual PostGreSQL database connection interface: +# The actual PostgreSQL database connection interface: class DB: """Wrapper class for the _pg connection type.""" @@ -1572,9 +1568,15 @@ def __del__(self): except AttributeError: db = None if db: - db.set_cast_hook(None) + try: + db.set_cast_hook(None) + except TypeError: + pass # probably already closed if self._closeable: - db.close() + try: + db.close() + except InternalError: + pass # probably already closed # Auxiliary methods @@ -1631,7 +1633,10 @@ def close(self): # Wraps shared library function so we can track state. if self._closeable: if self.db: - self.db.set_cast_hook(None) + try: + self.db.set_cast_hook(None) + except TypeError: + pass # probably already closed self.db.close() self.db = None else: @@ -1662,6 +1667,7 @@ def reopen(self): if self.db: self.db.set_cast_hook(None) self.db.close() + db.set_cast_hook(self.dbtypes.typecast) self.db = db def begin(self, mode=None): diff --git a/pgdb.py b/pgdb.py index fe52df4a..38ebeafb 100644 --- a/pgdb.py +++ b/pgdb.py @@ -74,7 +74,11 @@ from decimal import Decimal from uuid import UUID as Uuid from math import isnan, isinf -from collections import namedtuple, Iterable +try: + from collections.abc import Iterable +except ImportError: # Python < 3.3 + from collections import Iterable +from collections import namedtuple from keyword import iskeyword from functools import partial from re import compile as regex @@ -493,8 +497,8 @@ class Typecasts(dict): The cast functions get passed the string representation of a value in the database which they need to convert to a Python object. The - passed string will never be None since NULL values are already be - handled before the cast function is called. + passed string will never be None since NULL values are already handled + before the cast function is called. """ # the default cast functions diff --git a/pgmodule.c b/pgmodule.c index c25b5776..e70aaa39 100644 --- a/pgmodule.c +++ b/pgmodule.c @@ -22,7 +22,7 @@ * AUTHOR HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, * ENHANCEMENTS, OR MODIFICATIONS. * - * Further modifications copyright 1997 to 2017 by D'Arcy J.M. Cain + * Further modifications copyright 1997 to 2019 by D'Arcy J.M. Cain * (darcy@PyGreSQL.org) subject to the same terms and conditions as above. * */ @@ -664,11 +664,11 @@ cast_array(char *s, Py_ssize_t size, int encoding, if (s == end || *s++ != '[') break; while (s != end && *s == ' ') ++s; if (s != end && (*s == '+' || *s == '-')) ++s; - if (s == end || *s <= '0' || *s >= '9') break; + if (s == end || *s < '0' || *s > '9') break; while (s != end && *s >= '0' && *s <= '9') ++s; if (s == end || *s++ != ':') break; if (s != end && (*s == '+' || *s == '-')) ++s; - if (s == end || *s <= '0' || *s >= '9') break; + if (s == end || *s < '0' || *s > '9') break; while (s != end && *s >= '0' && *s <= '9') ++s; if (s == end || *s++ != ']') break; while (s != end && *s == ' ') ++s; @@ -2224,14 +2224,14 @@ connQuery(connObject *self, PyObject *args) { /* prepare arguments */ PyObject **str, **s; - char **parms, **p; + const char **parms, **p; register int i; str = (PyObject **)PyMem_Malloc(nparms * sizeof(*str)); - parms = (char **)PyMem_Malloc(nparms * sizeof(*parms)); + parms = (const char **)PyMem_Malloc(nparms * sizeof(*parms)); if (!str || !parms) { - PyMem_Free(parms); PyMem_Free(str); + PyMem_Free((void *)parms); PyMem_Free(str); Py_XDECREF(query_obj); Py_XDECREF(param_obj); return PyErr_NoMemory(); } @@ -2256,7 +2256,7 @@ connQuery(connObject *self, PyObject *args) PyObject *str_obj = get_encoded_string(obj, encoding); if (!str_obj) { - PyMem_Free(parms); + PyMem_Free((void *)parms); while (s != str) { s--; Py_DECREF(*s); } PyMem_Free(str); Py_XDECREF(query_obj); @@ -2272,7 +2272,7 @@ connQuery(connObject *self, PyObject *args) PyObject *str_obj = PyObject_Str(obj); if (!str_obj) { - PyMem_Free(parms); + PyMem_Free((void *)parms); while (s != str) { s--; Py_DECREF(*s); } PyMem_Free(str); Py_XDECREF(query_obj); @@ -2288,10 +2288,10 @@ connQuery(connObject *self, PyObject *args) Py_BEGIN_ALLOW_THREADS result = PQexecParams(self->cnx, query, nparms, - NULL, (const char * const *)parms, NULL, NULL, 0); + NULL, parms, NULL, NULL, 0); Py_END_ALLOW_THREADS - PyMem_Free(parms); + PyMem_Free((void *)parms); while (s != str) { s--; Py_DECREF(*s); } PyMem_Free(str); } @@ -2338,11 +2338,13 @@ connQuery(connObject *self, PyObject *args) { char *ret = PQcmdTuples(result); - PQclear(result); if (ret[0]) /* return number of rows affected */ { - return PyStr_FromString(ret); + PyObject *obj = PyStr_FromString(ret); + PQclear(result); + return obj; } + PQclear(result); Py_INCREF(Py_None); return Py_None; } @@ -3486,7 +3488,7 @@ connGetAttr(connObject *self, PyObject *nameobj) if (!strcmp(name, "host")) { char *r = PQhost(self->cnx); - if (!r || r[0] == '/') /* Pg 9.6 can return a Unix socket path */ + if (!r || r[0] == '/') /* Pg >= 9.6 can return a Unix socket path */ r = "localhost"; return PyStr_FromString(r); } diff --git a/setup.py b/setup.py index 62029825..1130e99d 100755 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ #! /usr/bin/python # $Id$ -"""Setup script for PyGreSQL version 5.0.4 +"""Setup script for PyGreSQL version 5.0.7 PyGreSQL is an open-source Python module that interfaces to a PostgreSQL database. It embeds the PostgreSQL query library to allow @@ -13,7 +13,7 @@ * setup script created 2000 by Mark Alexander * improved 2000 by Jeremy Hylton * improved 2001 by Gerhard Haering -* improved 2006 to 2017 by Christoph Zwerschke +* improved 2006 to 2018 by Christoph Zwerschke Prerequisites to be installed: * Python including devel package (header files and distutils) @@ -21,7 +21,8 @@ * PostgreSQL pg_config tool (usually included in the devel package) (the Windows installer has it as part of the database server feature) -The supported versions are Python 2.6-2.7, 3.3-3.6 and PostgreSQL 9.0-9.6. +PyGreSQL currently supports Python versions 2.6, 2.7 and 3.3 to 3.7, +and PostgreSQL versions 9.0 to 9.6 and 10. Use as follows: python setup.py build # to build the module @@ -32,7 +33,7 @@ """ -version = '5.0.5' +version = '5.0.7' import sys @@ -180,7 +181,7 @@ def finalize_options(self): url="http://www.pygresql.org", download_url="http://www.pygresql.org/download/", platforms=["any"], - license="Python", + license="PostgreSQL", py_modules=py_modules, ext_modules=[Extension('_pg', c_sources, include_dirs=include_dirs, library_dirs=library_dirs, @@ -192,7 +193,7 @@ def finalize_options(self): classifiers=[ "Development Status :: 6 - Mature", "Intended Audience :: Developers", - "License :: OSI Approved :: Python Software Foundation License", + "License :: OSI Approved :: PostgreSQL License", "Operating System :: OS Independent", "Programming Language :: C", 'Programming Language :: Python', @@ -204,6 +205,7 @@ def finalize_options(self): 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', "Programming Language :: SQL", "Topic :: Database", "Topic :: Database :: Front-Ends", diff --git a/tests/test_classic_connection.py b/tests/test_classic_connection.py index bd449fad..4ea92940 100755 --- a/tests/test_classic_connection.py +++ b/tests/test_classic_connection.py @@ -139,9 +139,12 @@ def testAttributeError(self): @unittest.skipIf(do_not_ask_for_host, do_not_ask_for_host_reason) def testAttributeHost(self): - def_host = 'localhost' + if dbhost and not dbhost.startswith('/'): + host = dbhost + else: + host = 'localhost' self.assertIsInstance(self.connection.host, str) - self.assertEqual(self.connection.host, dbhost or def_host) + self.assertEqual(self.connection.host, host) def testAttributeOptions(self): no_options = '' @@ -160,7 +163,7 @@ def testAttributeProtocolVersion(self): def testAttributeServerVersion(self): server_version = self.connection.server_version self.assertIsInstance(server_version, int) - self.assertTrue(70400 <= server_version < 100000) + self.assertTrue(90000 <= server_version < 120000) def testAttributeStatus(self): status_ok = 1 @@ -271,19 +274,19 @@ def testMethodParameter(self): self.assertRaises(TypeError, parameter) r = parameter('this server setting does not exist') self.assertIsNone(r) - s = query('show server_version').getresult()[0][0].upper() + s = query('show server_version').getresult()[0][0] self.assertIsNotNone(s) r = parameter('server_version') self.assertEqual(r, s) - s = query('show server_encoding').getresult()[0][0].upper() + s = query('show server_encoding').getresult()[0][0] self.assertIsNotNone(s) r = parameter('server_encoding') self.assertEqual(r, s) - s = query('show client_encoding').getresult()[0][0].upper() + s = query('show client_encoding').getresult()[0][0] self.assertIsNotNone(s) r = parameter('client_encoding') self.assertEqual(r, s) - s = query('show server_encoding').getresult()[0][0].upper() + s = query('show server_encoding').getresult()[0][0] self.assertIsNotNone(s) r = parameter('server_encoding') self.assertEqual(r, s) diff --git a/tests/test_classic_dbwrapper.py b/tests/test_classic_dbwrapper.py index 71404d02..4ec906e3 100755 --- a/tests/test_classic_dbwrapper.py +++ b/tests/test_classic_dbwrapper.py @@ -244,11 +244,13 @@ def testAttributeError(self): @unittest.skipIf(do_not_ask_for_host, do_not_ask_for_host_reason) def testAttributeHost(self): - def_host = 'localhost' - host = self.db.host - self.assertIsInstance(host, str) - self.assertEqual(host, dbhost or def_host) - self.assertEqual(host, self.db.db.host) + if dbhost and not dbhost.startswith('/'): + host = dbhost + else: + host = 'localhost' + self.assertIsInstance(self.db.host, str) + self.assertEqual(self.db.host, host) + self.assertEqual(self.db.db.host, host) def testAttributeOptions(self): no_options = '' @@ -272,7 +274,7 @@ def testAttributeProtocolVersion(self): def testAttributeServerVersion(self): server_version = self.db.server_version self.assertIsInstance(server_version, int) - self.assertTrue(70400 <= server_version < 100000) + self.assertTrue(90000 <= server_version < 120000) self.assertEqual(server_version, self.db.db.server_version) def testAttributeStatus(self): @@ -372,26 +374,42 @@ def testMethodReopen(self): def testExistingConnection(self): db = pg.DB(self.db.db) + self.assertIsNotNone(db.db) self.assertEqual(self.db.db, db.db) - self.assertTrue(db.db) db.close() - self.assertTrue(db.db) + self.assertIsNotNone(db.db) + self.assertIsNotNone(self.db.db) db.reopen() - self.assertTrue(db.db) + self.assertIsNotNone(db.db) + self.assertEqual(self.db.db, db.db) db.close() - self.assertTrue(db.db) + self.assertIsNotNone(db.db) db = pg.DB(self.db) self.assertEqual(self.db.db, db.db) db = pg.DB(db=self.db.db) self.assertEqual(self.db.db, db.db) - class DB2: - pass + def testExistingDbApi2Connection(self): + + class DBApi2Con: + + def __init__(self, cnx): + self._cnx = cnx - db2 = DB2() - db2._cnx = self.db.db + def close(self): + self._cnx.close() + + db2 = DBApi2Con(self.db.db) db = pg.DB(db2) self.assertEqual(self.db.db, db.db) + db.close() + self.assertIsNotNone(db.db) + db.reopen() + self.assertIsNotNone(db.db) + self.assertEqual(self.db.db, db.db) + db.close() + self.assertIsNotNone(db.db) + db2.close() class TestDBClass(unittest.TestCase): diff --git a/tests/test_classic_functions.py b/tests/test_classic_functions.py index 284665cf..e168e6d2 100755 --- a/tests/test_classic_functions.py +++ b/tests/test_classic_functions.py @@ -202,6 +202,8 @@ class TestParseArray(unittest.TestCase): ('[-1:+1]={1,2,3}', int, [1, 2, 3]), ('[-3:-1]={1,2,3}', int, [1, 2, 3]), ('[+1:+3]={1,2,3}', int, [1, 2, 3]), + ('[0:2]={1,2,3}', int, [1, 2, 3]), + ('[7:9]={1,2,3}', int, [1, 2, 3]), ('[]={1,2,3}', int, ValueError), ('[1:]={1,2,3}', int, ValueError), ('[:3]={1,2,3}', int, ValueError), diff --git a/tests/test_dbapi20.py b/tests/test_dbapi20.py index c76744cc..926bc8ce 100755 --- a/tests/test_dbapi20.py +++ b/tests/test_dbapi20.py @@ -439,7 +439,7 @@ def test_type_cache_typecast(self): def test_cursor_iteration(self): con = self._connect() cur = con.cursor() - cur.execute("select 1 union select 2 union select 3") + cur.execute("select 1 union select 2 union select 3 order by 1") self.assertEqual([r[0] for r in cur], [1, 2, 3]) def test_cursor_invalidation(self): diff --git a/tests/test_dbapi20_copy.py b/tests/test_dbapi20_copy.py index f7b572df..9d5d04ce 100644 --- a/tests/test_dbapi20_copy.py +++ b/tests/test_dbapi20_copy.py @@ -15,7 +15,10 @@ except ImportError: import unittest -from collections import Iterable +try: + from collections.abc import Iterable +except ImportError: # Python < 3.3 + from collections import Iterable import pgdb # the module under test diff --git a/tox.ini b/tox.ini index 17e52b3e..4f7252b2 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,11 @@ -# config file for tox 2.0 +# config file for tox 2 [tox] -envlist = py{26,27,33,34,35,36} +envlist = py{26,27,33,34,35,36,37} [testenv] deps = py26: unittest2 commands = py26: unit2 discover [] - py{27,33,34,35,36}: python -m unittest discover [] + py{27,33,34,35,36,37}: python -m unittest discover []