I proofread the docs. PFA patch with suggested changes.
--
Justin Pryzby
System Administrator
Telsasoft
+1-952-707-8581
Index: docs/contents/general.rst
===================================================================
--- docs/contents/general.rst (revision 961)
+++ docs/contents/general.rst (working copy)
@@ -19,9 +19,8 @@
from Python that has been developed by the Python DB-SIG in 1999.
The authoritative programming information for the DB-API is :pep:`0249`.
-Both Python modules utilize the same lower level C extension module that
-serves as a wrapper for the C API to PostgreSQL that is available in form
-of the so-called "libpq" library.
+Both Python modules utilize the same low-level C library, which
+serves as a wrapper for the "libpq" library interface to PostgreSQL.
This means you must have the libpq library installed as a shared library
on your client computer, in a version that is supported by PyGreSQL.
Index: docs/contents/install.rst
===================================================================
--- docs/contents/install.rst (revision 961)
+++ docs/contents/install.rst (working copy)
@@ -4,9 +4,9 @@
General
-------
-You must first have installed Python and PostgreSQL on your system.
+You must first install Python and PostgreSQL on your system.
If you want to access remote database only, you don't need to install
-the full PostgreSQL server, but only the C interface (libpq). If you
+the full PostgreSQL server, but only the library interface (libpq). If you
are on Windows, make sure that the directory with libpq.dll is in your
``PATH`` environment variable.
@@ -14,22 +14,22 @@
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.
+_pg.so, and two pure Python wrapper modules called pg.py and pgdb.py.
All three files will be installed directly into the Python site-packages
-directory. To uninstall PyGreSQL, simply remove these three files again.
+directory. To uninstall PyGreSQL, simply remove these three files.
Installing with Pip
-------------------
-This is the most easy way to install PyGreSQL if you have "pip" installed
-on your computer. Just run the following command in your terminal::
+This is the most easy way to install PyGreSQL if you have "pip" installed.
+Just run the following command in your terminal::
pip install PyGreSQL
This will automatically try to find and download a distribution on the
`Python Package Index <https://pypi.python.org/>`_ that matches your operating
-system and Python version and install it on your computer.
+system and Python version and install it.
Installing from a Binary Distribution
@@ -40,7 +40,7 @@
and install a distribution.
When you download the source distribution, you will need to compile the
-C extensions, for which you need a C compiler installed on your computer.
+C library, for which you need a C compiler installed.
If you don't want to install a C compiler or avoid possible problems
with the compilation, you can search for a pre-compiled binary distribution
of PyGreSQL on the Python Package Index or the PyGreSQL homepage.
@@ -86,7 +86,7 @@
Compiling Manually
~~~~~~~~~~~~~~~~~~
-The source file for compiling the dynamic module is called pgmodule.c.
+The source file for compiling the shared library is pgmodule.c.
You have two options. You can compile PyGreSQL as a stand-alone module
or you can build it into the Python interpreter.
Index: docs/contents/pg/adaptation.rst
===================================================================
--- docs/contents/pg/adaptation.rst (revision 961)
+++ docs/contents/pg/adaptation.rst (working copy)
@@ -64,7 +64,7 @@
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::
@@ -74,12 +74,12 @@
This seems to work at a first glance, but you will notice an error as soon as
you try to use a login name containing a single quote. Even worse, this error
-can be exploited through a so called "SQL injection", where an attacker inserts
+can be exploited through so-called "SQL injection", where an attacker inserts
malicious SQL statements into the query that you never intended to be executed.
-For instance, with a login name something like ``' OR ''='`` the user could
+For instance, with a login name something like ``' OR ''='`` the attacker could
easily log in and see the user data of another user in the database.
-One solution for this problem would be to clean your input from "dangerous"
+One solution for this problem would be to cleanse your input of "dangerous"
characters like the single quote, but this is tedious and it is likely that
you overlook something or break the application e.g. for users with names
like "D'Arcy". A better solution is to use the escaping functions provided
@@ -164,7 +164,7 @@
since in an INSERT statement or a WHERE clause comparing the parameter to a
table column the data type will be clear from the context.
-When binding the parameters to a query, PyGreSQL does not only adapt the basic
+When binding the parameters to a query, PyGreSQL not only adapts the basic
types like ``int``, ``float``, ``bool`` and ``str``, but also tries to make
sense of Python lists and tuples.
@@ -222,8 +222,8 @@
{'count': 1000, 'item': inventory_item(name='fuzzy dice',
supplier_id=42, price=Decimal('1.99'))}
-However, we may not want to use named tuples, but custom Python classes
-to hold our values, like this one::
+Perhaps we want to use custom Python classes instead of named tuples to hold
+our values::
>>> class InventoryItem:
...
Index: docs/contents/pg/connection.rst
===================================================================
--- docs/contents/pg/connection.rst (revision 961)
+++ docs/contents/pg/connection.rst (working copy)
@@ -44,9 +44,9 @@
This method simply sends a SQL query to the database. If the query is an
insert statement that inserted exactly one row into a table that has OIDs, the
-return value is the OID of the newly inserted row. If the query is an update
+return value is the OID of the newly inserted rows as an integer. If the query is an update
or delete statement, or an insert statement that did not insert exactly one
-row in a table with OIDs, then the number of rows affected is returned as a
+row, or on a table without OIDs, then the number of rows affected is returned as a
string. If it is a statement that returns rows as a result (usually a select
statement, but maybe also an ``"insert/update ... returning"`` statement),
this method returns a :class:`Query` that can be accessed via the
@@ -56,7 +56,7 @@
The SQL command may optionally contain positional parameters of the form
``$1``, ``$2``, etc instead of literal data, in which case the values
-have to be supplied separately as a tuple. The values are substituted by
+must be supplied separately as a tuple. The values are substituted by
the database in such a way that they don't need to be escaped, making this
an effective way to pass arbitrary or unknown data without worrying about
SQL injection or syntax errors.
@@ -91,9 +91,9 @@
This method works exactly like :meth:`Connection.query` except that instead
of passing the command itself, you pass the name of a prepared statement.
-An empty name corresponds to the unnamed statement. You must have created
-the corresponding named or unnamed statement with :meth:`Connection.prepare`
-before, or an :exc:`pg.OperationalError` will be raised.
+An empty name corresponds to the unnamed statement. You must have previously created
+the corresponding named or unnamed statement with :meth:`Connection.prepare`,
+or an :exc:`pg.OperationalError` will be raised.
.. versionadded:: 5.1
@@ -111,8 +111,8 @@
:raises TypeError: invalid connection
:raises pg.ProgrammingError: error in query or duplicate query
-This method creates a prepared statement for the given command with the
-given name for later execution with the :meth:`Connection.query_prepared`
+This method creates a prepared statement with the specified name for the given command
+for later execution with the :meth:`Connection.query_prepared`
method. The name can be empty to create an unnamed statement, in which case
any pre-existing unnamed statement is automatically replaced; otherwise a
:exc:`pg.ProgrammingError` is raised if the statement name is already defined
@@ -317,7 +317,7 @@
.. 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_notice_receiver -- custom notice receiver
-------------------------------------------------
@@ -451,8 +451,8 @@
:raises TypeError: invalid connection, bad parameter type, or too many parameters
:raises ValueError: bad OID value (0 is invalid_oid)
-This method allows to reuse a formerly created large object through the
-:class:`LargeObject` interface, providing the user have its OID.
+This method allows reusing a previously created large object through the
+:class:`LargeObject` interface, provided the user has its OID.
loimport -- import a file to a large object [LO]
------------------------------------------------
Index: docs/contents/pg/db_types.rst
===================================================================
--- docs/contents/pg/db_types.rst (revision 961)
+++ docs/contents/pg/db_types.rst (working copy)
@@ -89,6 +89,6 @@
objects of the running connections.
Also note that the typecasting for all of the basic types happens already
- in the C extension module. The typecast functions that can be set with
+ in the C module. The typecast functions that can be set with
the above methods are only called for the types that are not already
- supported by the C extension module.
+ supported by the C module.
Index: docs/contents/pg/db_wrapper.rst
===================================================================
--- docs/contents/pg/db_wrapper.rst (revision 961)
+++ docs/contents/pg/db_wrapper.rst (working copy)
@@ -135,6 +135,7 @@
By default, only a limited number of simple types will be returned.
You can get the regular types after enabling this by calling the
+-- after enabling this ????
:meth:`DB.use_regtypes` method.
has_table_privilege -- check table privilege
@@ -182,8 +183,8 @@
of all existing configuration parameters.
Note that you can request most of the important parameters also using
-:meth:`Connection.parameter()` which does not involve a database query
-like it is the case for :meth:`DB.get_parameter` and :meth:`DB.set_parameter`.
+:meth:`Connection.parameter()` which does not involve a database query,
+which not true for :meth:`DB.get_parameter` and :meth:`DB.set_parameter`.
.. versionadded:: 4.2
@@ -246,8 +247,13 @@
Commit a transaction
This commits the current transaction. All changes made by the
- transaction become visible to others and are guaranteed to be
- durable if a crash occurs.
+ transaction become visible to others. After returning, the changes are
+ guaranteed to be durable, even if a crash occurs.
+# XXX: it's not really pygres place to say this, so maybe we shouldn't?
+# it's not true if someone disables full_page_writes or fsync,
+# Or if someone enables write cache on drives
+# Or if someone sets barriers=0
+# Or if someone forgets to replace cache battery...
.. method:: DB.end()
@@ -261,8 +267,8 @@
:param str name: optionally, roll back to the specified savepoint
- This rolls back the current transaction and causes all the updates
- made by the transaction to be discarded.
+ This rolls back the current transaction. All changes
+ made by the transaction are be discarded.
.. method:: DB.abort()
@@ -314,11 +320,12 @@
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 if row was passed as aa 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
munged as ``oid(table)`` using the actual name of the table.
+XXX: what if someone calls their column "oid(t)" ?
Note that since PyGreSQL 5.0 this will return the value of an array
type column as a Python list by default.
@@ -346,7 +353,7 @@
to pick up values modified by rules, triggers, etc.
Note that since PyGreSQL 5.0 it is possible to insert a value for an
-array type column by passing it as Python list.
+array type column by passing it as a Python list.
update -- update a row in a database table
------------------------------------------
@@ -372,10 +379,11 @@
update due to triggers, rules, default values, etc.
Like insert, the dictionary is optional and updates will be performed
-on the fields in the keywords. There must be an OID or primary key
-either in the dictionary where the OID must be munged, or in the keywords
-where it can be simply the string ``'oid'``.
+on the fields in the keywords. There must be an OID or primary key either
+specified using the ``'oid'`` keyword or in the dictionary, in which case the
+OID must be munged.
+
upsert -- insert a row with conflict resolution
-----------------------------------------------
@@ -391,8 +399,8 @@
:raises pg.ProgrammingError: table has no primary key or missing privilege
This method inserts a row into a table, but instead of raising a
-ProgrammingError exception in case a row with the same primary key already
-exists, an update will be executed instead. This will be performed as a
+ProgrammingError exception in case of violating a constraint or unique index,
+an update will be executed instead. This will be performed as a
single atomic operation on the database, so race conditions can be avoided.
Like the insert method, the first parameter is the name of the table and the
@@ -524,13 +532,10 @@
:raises pg.OperationalError: prepared statement does not exist
This methods works like the :meth:`DB.query` method, except that instead of
-passing the SQL command, you pass the name of a prepared statement. If you
-pass an empty name, the unnamed statement will be executed.
+passing the SQL command, you pass the name of a prepared statement. If
+name is the empty string, the unnamed statement will be executed (see warning
+note in :meth:`DB.prepare`).
-You must have created the corresponding named or unnamed statement with
-the :meth:`DB.prepare` method before, otherwise an :exc:`pg.OperationalError`
-will be raised.
-
.. versionadded:: 5.1
prepare -- create a prepared statement
@@ -547,8 +552,8 @@
:raises TypeError: invalid connection
:raises pg.ProgrammingError: error in query or duplicate query
-This method creates a prepared statement for the given command with the
-given name for later execution with the :meth:`DB.query_prepared` method.
+This method creates a prepared statement for the given command
+for later execution with the :meth:`DB.query_prepared` method.
The name can be empty to create an unnamed statement, in which case any
pre-existing unnamed statement is automatically replaced; otherwise a
:exc:`pg.ProgrammingError` is raised if the statement name is already
@@ -571,11 +576,16 @@
db.query_prepared('change phone', ein, phone)
.. note::
-
We recommend always using named queries, since unnamed queries have a
limited lifetime and can be automatically replaced or destroyed by
various operations on the database.
+ We recommend avoiding use of unnamed prepared statements, because they
+ are replaced by other query operations.
+ In particular, the DB wrapper class issues queries behind the scenes
+ (for instance to find unknown database types) which can cause the users'
+ unnamed statement to be replaced.
+
.. versionadded:: 5.1
describe_prepared -- describe a prepared statement
@@ -806,8 +816,8 @@
This method escapes a string for use as an SQL identifier, such as a table,
column, or function name. This is useful when a user-supplied identifier
-might contain special characters that would otherwise not be interpreted
-as part of the identifier by the SQL parser, or when the identifier might
+might contain special characters that would otherwise be misinterpreted
+by the SQL parser, or when the identifier might
contain upper case characters whose case should be preserved.
.. versionadded:: 4.1
@@ -890,7 +900,7 @@
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
@@ -909,7 +919,7 @@
of type names is used can be changed by calling :meth:`DB.get_regtypes`.
If you pass a boolean, it sets whether regular type names shall be used.
The method can also be used to check through its return value whether
-currently regular type names are used.
+regular type names are currently used.
.. versionadded:: 4.1
Index: docs/contents/pg/query.rst
===================================================================
--- docs/contents/pg/query.rst (revision 961)
+++ docs/contents/pg/query.rst (working copy)
@@ -21,7 +21,7 @@
:raises TypeError: too many (any) parameters
:raises MemoryError: internal memory error
-This method returns the list of the values returned by the query.
+This method returns query results as a list of tuples.
More information about this result may be accessed using
:meth:`Query.listfields`, :meth:`Query.fieldname`
and :meth:`Query.fieldnum` methods.
@@ -41,9 +41,8 @@
:raises TypeError: too many (any) parameters
: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
-used as the dictionary index.
+This method returns query results as a list of dictionaries indexed by field
+names.
Note that since PyGreSQL 5.0 this will return the values of array type
columns as Python lists.
@@ -61,8 +60,8 @@
:raises TypeError: named tuples not supported
:raises MemoryError: internal memory error
-This method returns the list of the values returned by the query
-with each row returned as a named tuple with proper field names.
+This method returns query results as a list of named tuples with proper field
+names.
Column names in the database that are not valid as field names for
named tuples (particularly, names starting with an underscore) are
@@ -84,7 +83,7 @@
:rtype: list
:raises TypeError: too many parameters
-This method returns the list of names of the fields defined for the
+This method returns the list of field names defined for the
query result. The fields are in the same order as the result values.
fieldname, fieldnum -- field name/number conversion
@@ -114,10 +113,10 @@
:raises TypeError: invalid connection, bad parameter type, or too many parameters
:raises ValueError: unknown field name
-This method returns a field number from its name. It can be used to
+This method returns a field number given its name. It can be used to
build a function that converts result list strings to their correct
type, using a hardcoded table definition. The number returned is the
-field rank in the result values list.
+field rank in the query result.
ntuples -- return number of tuples in query object
--------------------------------------------------
Index: docs/contents/postgres/advanced.rst
===================================================================
--- docs/contents/postgres/advanced.rst (revision 961)
+++ docs/contents/postgres/advanced.rst (working copy)
@@ -11,7 +11,7 @@
>>> from pg import DB
>>> db = DB()
- >>> query = query
+ >>> query = db.query
Inheritance
-----------
Index: docs/contents/postgres/basic.rst
===================================================================
--- docs/contents/postgres/basic.rst (revision 961)
+++ docs/contents/postgres/basic.rst (working copy)
@@ -71,9 +71,9 @@
>>> db.query("""INSERT INTO cities
... VALUES ('San Francisco', '(-194.0, 53.0)')""")
-You can also specify what column the values correspond to. The columns can
+You can also specify the columns to which the values correspond. The columns can
be specified in any order. You may also omit any number of columns,
-unknown precipitation below::
+such as with unknown precipitation, below::
>>> db.query("""INSERT INTO weather (date, city, temp_hi, temp_lo)
... VALUES ('11/29/1994', 'Hayward', 54, 37)""")
@@ -267,7 +267,7 @@
Hayward| 37| 54|San Francisco| 46| 50
(1 row)
-Now let's join two tables. The following joins the "weather" table and the
+Now let's join two different tables. The following joins the "weather" table and the
"cities" table::
>>> print(db.query("""SELECT city, location, prcp, date
Index: docs/contents/postgres/func.rst
===================================================================
--- docs/contents/postgres/func.rst (revision 961)
+++ docs/contents/postgres/func.rst (working copy)
@@ -22,7 +22,7 @@
>>> 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"))
@@ -89,7 +89,7 @@
... 'None'::varchar(16) AS dept
... $$ LANGUAGE SQL""")
-You can then project a column out of resulting the tuple by using the
+You can then extract a column out of the resulting tuple by using the
"function notation" for projection columns (i.e. ``bar(foo)`` is equivalent
to ``foo.bar``). Note that ``new_emp().name`` isn't supported::
@@ -131,7 +131,7 @@
Ginger| 4800| 30|candy
(5 rows)
>>> query("""CREATE FUNCTION clean_EMP () RETURNS int4 AS
- ... 'DELETE FROM EMP WHERE EMP.salary <= 0;
+ ... 'DELETE FROM EMP WHERE EMP.salary < 0;
... SELECT 1 AS ignore_this'
... LANGUAGE SQL""")
>>> query("SELECT clean_EMP()")
Index: docs/contents/postgres/syscat.rst
===================================================================
--- docs/contents/postgres/syscat.rst (revision 961)
+++ docs/contents/postgres/syscat.rst (working copy)
@@ -7,8 +7,8 @@
such as information about tables and columns, and internal bookkeeping
information. You can drop and recreate the tables, add columns, insert and
update values, and severely mess up your system that way. Normally, one
-should not change the system catalogs by hand, there are always SQL commands
-to do that. For example, CREATE DATABASE inserts a row into the *pg_database*
+should not change the system catalogs by hand: there are SQL commands
+to make all supported changes. For example, CREATE DATABASE inserts a row into the *pg_database*
catalog — and actually creates the database on disk.
It this section we want to show examples for how to parse some of the system
@@ -19,7 +19,7 @@
>>> from pg import DB
>>> db = DB()
- >>> query = query
+ >>> query = db.query
Lists indices
-------------
@@ -31,7 +31,7 @@
FROM pg_class bc, pg_class ic, pg_index i, pg_attribute a
WHERE i.indrelid = bc.oid AND i.indexrelid = ic.oid
AND i.indkey[0] = a.attnum AND a.attrelid = bc.oid
- AND NOT a.attisdropped
+ AND NOT a.attisdropped AND a.attnum>0
ORDER BY class_name, index_name, attname"""))
@@ -38,14 +38,14 @@
List user defined attributes
----------------------------
-This query lists all user defined attributes and their type
-in user-defined classes::
+This query lists all user-defined attributes and their types
+in user-defined tables::
- print(query("""SELECT c.relname, a.attname, t.typname
- FROM pg_class c, pg_attribute a, pg_type t
- WHERE c.relkind = 'r' and c.relname !~ '^pg_'
- AND c.relname !~ '^Inv' and a.attnum > 0
- AND a.attrelid = c.oid and a.atttypid = t.oid
+ print(query("""SELECT c.relname, a.attname, format_type(a.atttypid, a.atttypmod)
+ FROM pg_class c, pg_attribute a
+ WHERE c.relkind = 'r' and c.relnamespace!=ALL(ARRAY['pg_catalog','pg_toast']::regnamespace[])
+ AND c.relname !~ '^Inv' and a.attnum > 0 -- what is Inv ??
+ AND a.attrelid = c.oid
AND NOT a.attisdropped
ORDER BY relname, attname"""))
@@ -54,7 +54,7 @@
This query lists all user defined base types::
- print(query("""SELECT r.rolname, t.typname
+ print(query("""SELECT r.rolname, t.typname -- format_type?
FROM pg_type t, pg_authid r
WHERE r.oid = t.typowner
AND t.typrelid = '0'::oid and t.typelem = '0'::oid
@@ -62,7 +62,7 @@
ORDER BY rolname, typname"""))
-List operators
+List operators
---------------
This query lists all right-unary operators::
Index: docs/contents/tutorial.rst
===================================================================
--- docs/contents/tutorial.rst (revision 961)
+++ docs/contents/tutorial.rst (working copy)
@@ -13,8 +13,7 @@
.. py:currentmodule:: pg
-The first thing you need to do anything with your PostgreSQL database is
-to create a database connection.
+Before doing anything else, it's necessary to create a database connection.
To do this, simply import the :class:`DB` wrapper class and create an
instance of it, passing the necessary connection parameters, like this::
@@ -194,7 +193,7 @@
>>> con = connect(database='testdb', host='pgserver:5432',
... user='scott', password='tiger')
-Note that like in the classic interface, you can omit parameters if they
+As in the classic interface, you can omit parameters if they
are the default values used by PostgreSQL.
To do anything with the connection, you need to request a cursor object
@@ -203,13 +202,13 @@
>>> cursor = con.cursor()
-The cursor now has a method that lets you execute database queries::
+The cursor has a method that lets you execute database queries::
>>> cursor.execute("create table fruits("
... "id serial primary key, name varchar)")
-To insert data into the table, also can also use this method::
+You can also use this method to insert data into the table::
>>> cursor.execute("insert into fruits (name) values ('apple')")
@@ -217,7 +216,7 @@
>>> cursor.execute("insert into fruits (name) values (%s)", ('banana',))
-For inserting multiple rows at once, you can use the following method::
+To insert multiple rows at once, you can use the following method::
>>> more_fruits = 'cherimaya durian eggfruit fig grapefruit'.split()
>>> parameters = [(name,) for name in more_fruits]
@@ -231,15 +230,15 @@
Also note that the DB API 2.0 interface does not have an autocommit as you
may be used from PostgreSQL. So in order to make these inserts permanent,
-you need to commit them to the database first::
+you need to commit them to the database::
>>> con.commit()
If you end the program without calling the commit method of the connection,
-or if you call the rollback method of the connection, then all the changes
+or if you call the rollback method of the connection, then the changes
will be discarded.
-In a similar way, you can also update or delete rows in the database,
+In a similar way, you can update or delete rows in the database,
executing UPDATE or DELETE statements instead of INSERT statements.
To fetch rows from the database, execute a SELECT statement first. Then
@@ -251,8 +250,8 @@
Row(id=1, name='apple')
The result is a named tuple. This means you can access its elements either
-using an index number like in an ordinary tuple, or using the column name
-like you access object attributes.
+using an index number as for an ordinary tuple, or using the column name
+as for access to object attributes.
To fetch all rows of the query, use this method instead::
@@ -274,4 +273,4 @@
>>> cur.close()
>>> con.close()
-For more advanced features and details, see the reference: :doc:`pgdb/index`
\ No newline at end of file
+For more advanced features and details, see the reference: :doc:`pgdb/index`
_______________________________________________
PyGreSQL mailing list
[email protected]
https://mail.vex.net/mailman/listinfo/pygresql