diff --git a/asyncpg/connection.py b/asyncpg/connection.py index 59945df4..9e94de43 100644 --- a/asyncpg/connection.py +++ b/asyncpg/connection.py @@ -378,11 +378,25 @@ async def copy_from_table(self, table_name, *, output, :return: The status string of the COPY command. - .. versionadded:: 0.11.0 + Example: + + .. code-block:: pycon + + >>> import asyncpg + >>> import asyncio + >>> async def run(): + ... con = await asyncpg.connect(user='postgres') + ... result = await con.copy_from_table( + ... 'mytable', columns=('foo', 'bar'), + ... output='file.csv', format='csv') + ... print(result) + >>> asyncio.get_event_loop().run_until_complete(run()) + 'COPY 100' .. _`COPY statement documentation`: https://www.postgresql.org/docs/\ current/static/sql-copy.html + .. versionadded:: 0.11.0 """ tabname = utils._quote_ident(table_name) if schema_name: @@ -415,7 +429,7 @@ async def copy_from_query(self, query, *args, output, :param str query: The query to copy the results of. - :param *args: + :param \*args: Query arguments. :param output: @@ -432,11 +446,25 @@ async def copy_from_query(self, query, *args, output, :return: The status string of the COPY command. - .. versionadded:: 0.11.0 + Example: + + .. code-block:: pycon + + >>> import asyncpg + >>> import asyncio + >>> async def run(): + ... con = await asyncpg.connect(user='postgres') + ... result = await con.copy_from_query( + ... 'SELECT foo, bar FROM mytable WHERE foo > $1', 10, + ... output='file.csv', format='csv') + ... print(result) + >>> asyncio.get_event_loop().run_until_complete(run()) + 'COPY 10' .. _`COPY statement documentation`: https://www.postgresql.org/docs/\ current/static/sql-copy.html + .. versionadded:: 0.11.0 """ opts = self._format_copy_opts( format=format, oids=oids, delimiter=delimiter, @@ -469,7 +497,7 @@ async def copy_to_table(self, table_name, *, source, or a :term:`file-like object `, or an :term:`asynchronous iterable ` that returns ``bytes``, or an object supporting the - :term:`buffer protocol `. + :ref:`buffer protocol `. :param list columns: An optional list of column names to copy. @@ -485,11 +513,24 @@ async def copy_to_table(self, table_name, *, source, :return: The status string of the COPY command. - .. versionadded:: 0.11.0 + Example: + + .. code-block:: pycon + + >>> import asyncpg + >>> import asyncio + >>> async def run(): + ... con = await asyncpg.connect(user='postgres') + ... result = await con.copy_to_table( + ... 'mytable', source='datafile.tbl') + .... print(result) + >>> asyncio.get_event_loop().run_until_complete(run()) + 'COPY 140000' .. _`COPY statement documentation`: https://www.postgresql.org/docs/\ current/static/sql-copy.html + .. versionadded:: 0.11.0 """ tabname = utils._quote_ident(table_name) if schema_name: @@ -535,6 +576,22 @@ async def copy_records_to_table(self, table_name, *, records, :return: The status string of the COPY command. + Example: + + .. code-block:: pycon + + >>> import asyncpg + >>> import asyncio + >>> async def run(): + ... con = await asyncpg.connect(user='postgres') + ... result = await con.copy_records_to_table( + ... 'mytable', records=[ + ... (1, 'foo', 'bar'), + ... (2, 'ham', 'spam')]) + .... print(result) + >>> asyncio.get_event_loop().run_until_complete(run()) + 'COPY 2' + .. versionadded:: 0.11.0 """ tabname = utils._quote_ident(table_name) diff --git a/docs/api/index.rst b/docs/api/index.rst index ef485848..a2933088 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -233,8 +233,8 @@ It's also possible to create cursors from prepared statements: .. _asyncpg-api-pool: -Connection Pool -=============== +Connection Pools +================ .. autofunction:: asyncpg.pool.create_pool @@ -326,11 +326,8 @@ items either by a numeric index or by a field name: 'UTF8' -Introspection -============= - -.. autoclass:: asyncpg.types.Type() - :members: +Data Types +========== -.. autoclass:: asyncpg.types.Attribute() +.. automodule:: asyncpg.types :members: diff --git a/docs/examples.rst b/docs/examples.rst deleted file mode 100644 index 67bf568c..00000000 --- a/docs/examples.rst +++ /dev/null @@ -1,47 +0,0 @@ -.. _asyncpg-examples: - - -Usage Examples -============== - -Below is an example of how **asyncpg** can be used to implement a simple -Web service that computes the requested power of two. - - -.. code-block:: python - - import asyncio - import asyncpg - from aiohttp import web - - - async def handle(request): - """Handle incoming requests.""" - pool = request.app['pool'] - power = int(request.match_info.get('power', 10)) - - # Take a connection from the pool. - async with pool.acquire() as connection: - # Open a transaction. - async with connection.transaction(): - # Run the query passing the request argument. - result = await connection.fetchval('select 2 ^ $1', power) - return web.Response( - text="2 ^ {} is {}".format(power, result)) - - - async def init_app(): - """Initialize the application server.""" - app = web.Application() - # Create a database connection pool - app['pool'] = await asyncpg.create_pool(database='postgres', - user='postgres') - # Configure service routes - app.router.add_route('GET', '/{power:\d+}', handle) - app.router.add_route('GET', '/', handle) - return app - - - loop = asyncio.get_event_loop() - app = loop.run_until_complete(init_app()) - web.run_app(app) diff --git a/docs/faq.rst b/docs/faq.rst new file mode 100644 index 00000000..174deb83 --- /dev/null +++ b/docs/faq.rst @@ -0,0 +1,41 @@ +.. _asyncpg-faq: + + +Frequently Asked Questions +========================== + +Does asyncpg support DB-API? + No. DB-API is a synchronous API, while asyncpg is based + around an asynchronous I/O model. Thus, full drop-in compatibility + with DB-API is not possible and we decided to design asyncpg API + in a way that is better aligned with PostgreSQL architecture and + terminology. We will release a synchronous DB-API-compatible version + of asyncpg at some point in the future. + +Can I use asyncpg with SQLAlchemy ORM? + Short answer: no. asyncpg uses asynchronous execution model + and API, which is fundamentally incompatible with asyncpg. + However, it is possible to use asyncpg and SQLAlchemy Core + with the help of a third-party adapter, such as asyncpgsa_. + +Can I use dot-notation with :class:`asyncpg.Record`? It looks cleaner. + We decided against making :class:`asyncpg.Record` a named tuple + because we want to keep the ``Record`` method namespace separate + from the column namespace. + +Why can't I use a :ref:`cursor ` outside of a transaction? + Cursors created by a call to + :meth:`Connection.cursor() ` or + :meth:`PreparedStatement.cursor() \ + ` + cannot be used outside of a transaction. Any such attempt will result in + ``InterfaceError``. + To create a cursor usable outside of a transaction, use the + ``DECLARE ... CURSOR WITH HOLD`` SQL statement directly. + +Why do I get ``PostgresSyntaxError`` when using ``expression IN $1``? + ``expression IN $1`` is not a valid PostgreSQL syntax. To check + a value against a sequence use ``expression = any($1::mytype[])``, + where ``mytype`` is the array element type. + +.. _asyncpgsa: https://github.com/CanopyTax/asyncpgsa diff --git a/docs/index.rst b/docs/index.rst index 05e341b4..246c5641 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -24,5 +24,6 @@ Contents :maxdepth: 2 installation - examples + usage api/index + faq diff --git a/docs/usage.rst b/docs/usage.rst new file mode 100644 index 00000000..24fc1e44 --- /dev/null +++ b/docs/usage.rst @@ -0,0 +1,281 @@ +.. _asyncpg-examples: + + +asyncpg Usage +============= + +The interaction with the database normally starts with a call to +:func:`connect() `, which establishes +a new database session and returns a new +:class:`Connection ` instance, +which provides methods to run queries and manage transactions. + + +.. code-block:: python + + import asyncio + import asyncpg + import datetime + + async def main(): + # Establish a connection to an existing database named "test" + # as a "postgres" user. + conn = await asyncpg.connect('postgresql://postgres@localhost/test') + # Execute a statement to create a new table. + await conn.execute(''' + CREATE TABLE users( + id serial PRIMARY KEY, + name text, + dob date + ) + ''') + + # Insert a record into the created table. + await conn.execute(''' + INSERT INTO users(name, dob) VALUES($1, $2) + ''', 'Bob', datetime.date(1984, 3, 1)) + + # Select a row from the table. + row = await conn.fetchrow( + 'SELECT * FROM users WHERE name = $1', 'Bob') + # *row* now contains + # asyncpg.Record(id=1, name='Bob', dob=datetime.date(1984, 3, 1)) + + # Close the connection. + await conn.close() + + asyncio.get_event_loop().run_until_complete(main()) + + + +.. note:: + + asyncpg uses the native PostgreSQL syntax for query arguments: ``$n``. + + + +Type Conversion +--------------- + +asyncpg automatically converts PostgreSQL types to the corresponding Python +types and vice versa. All standard data types are supported out of the box, +including arrays, composite types, range types, enumerations and any +combination of them. It is possible to supply codecs for non-standard +types or override standard codecs. See :ref:`asyncpg-custom-codecs` for +more information. + +The table below shows the correspondence between PostgreSQL and Python types. + ++----------------------+-----------------------------------------------------+ +| PostgreSQL Type | Python Type | ++======================+=====================================================+ +| ``anyarray`` | :class:`list ` | ++----------------------+-----------------------------------------------------+ +| ``anyenum`` | :class:`str ` | ++----------------------+-----------------------------------------------------+ +| ``anyrange`` | :class:`asyncpg.Range ` | ++----------------------+-----------------------------------------------------+ +| ``record`` | :class:`asyncpg.Record`, | +| | :class:`tuple ` | ++----------------------+-----------------------------------------------------+ +| ``bit``, ``varbit`` | :class:`asyncpg.BitString `| ++----------------------+-----------------------------------------------------+ +| ``bool`` | :class:`bool ` | ++----------------------+-----------------------------------------------------+ +| ``box`` | :class:`asyncpg.Box ` | ++----------------------+-----------------------------------------------------+ +| ``bytea`` | :class:`bytes ` | ++----------------------+-----------------------------------------------------+ +| ``char``, ``name``, | :class:`str ` | +| ``varchar``, | | +| ``text``, | | +| ``xml`` | | ++----------------------+-----------------------------------------------------+ +| ``cidr`` | :class:`ipaddress.IPv4Network\ | +| | `, | +| | :class:`ipaddress.IPv6Netowrk\ | +| | ` | ++----------------------+-----------------------------------------------------+ +| ``inet`` | :class:`ipaddress.IPv4Network\ | +| | `, | +| | :class:`ipaddress.IPv6Netowrk\ | +| | `, | +| | :class:`ipaddress.IPv4Address\ | +| | `, | +| | :class:`ipaddress.IPv6Address\ | +| | ` | ++----------------------+-----------------------------------------------------+ +| ``macaddr`` | :class:`str ` | ++----------------------+-----------------------------------------------------+ +| ``circle`` | :class:`asyncpg.Circle ` | ++----------------------+-----------------------------------------------------+ +| ``date`` | :class:`datetime.date ` | ++----------------------+-----------------------------------------------------+ +| ``time`` | offset-naïve :class:`datetime.time \ | +| | ` | ++----------------------+-----------------------------------------------------+ +| ``time with | offset-aware :class:`datetime.time \ | +| timezone`` | ` | ++----------------------+-----------------------------------------------------+ +| ``timestamp`` | offset-naïve :class:`datetime.datetime \ | +| | ` | ++----------------------+-----------------------------------------------------+ +| ``timestamp with | offset-aware :class:`datetime.datetime \ | +| timezone`` | ` | ++----------------------+-----------------------------------------------------+ +| ``interval`` | :class:`datetime.timedelta \ | +| | ` | ++----------------------+-----------------------------------------------------+ +| ``float``, | :class:`float ` | +| ``double precision`` | | ++----------------------+-----------------------------------------------------+ +| ``smallint``, | :class:`int ` | +| ``integer``, | | +| ``bigint`` | | ++----------------------+-----------------------------------------------------+ +| ``numeric`` | :class:`Decimal ` | ++----------------------+-----------------------------------------------------+ +| ``json``, ``jsonb`` | :class:`str ` | ++----------------------+-----------------------------------------------------+ +| ``line`` | :class:`asyncpg.Line ` | ++----------------------+-----------------------------------------------------+ +| ``lseg`` | :class:`asyncpg.LineSegment \ | +| | ` | ++----------------------+-----------------------------------------------------+ +| ``money`` | :class:`str ` | ++----------------------+-----------------------------------------------------+ +| ``path`` | :class:`asyncpg.Path ` | ++----------------------+-----------------------------------------------------+ +| ``point`` | :class:`asyncpg.Point ` | ++----------------------+-----------------------------------------------------+ +| ``polygon`` | :class:`asyncpg.Polygon ` | ++----------------------+-----------------------------------------------------+ +| ``uuid`` | :class:`uuid.UUID ` | ++----------------------+-----------------------------------------------------+ + +All other types are encoded and decoded as text by default. + + +.. _asyncpg-custom-codecs: + +Custom Type Conversions +----------------------- + +asyncpg allows defining custom type conversion functions both for standard +and user-defined types using the :meth:`Connection.set_type_codec() \ +` and +:meth:`Connection.set_builtin_type_codec() \ +` methods. +The example below shows how to configure asyncpg to encode and decode +JSON values using the :mod:`json ` module. + +.. code-block:: python + + import asyncio + import asyncpg + import json + + + async def main(): + conn = await asyncpg.connect('postgresql://postgres@localhost/test') + + try: + def _encoder(value): + return json.dumps(value).encode('utf-8') + + def _decoder(value): + return json.loads(value.decode('utf-8')) + + await conn.set_type_codec( + 'json', encoder=_encoder, decoder=_decoder, + schema='pg_catalog', binary=True + ) + + data = {'foo': 'bar', 'spam': 1} + res = await conn.fetchval('SELECT $1::json', data) + + finally: + await conn.close() + + asyncio.get_event_loop().run_until_complete(main()) + + +Transactions +------------ + +To create transactions, the +:meth:`Connection.transaction() ` method +should be used. + +The most common way to use transactions is through an ``async with`` statement: + +.. code-block:: python + + async with connection.transaction(): + await connection.execute("INSERT INTO mytable VALUES(1, 2, 3)") + +.. note:: + + When not in an explicit transaction block, any changes to the database + will be applied immediately. This is also known as *auto-commit*. + +See the :ref:`asyncpg-api-transaction` API documentation for more information. + + +Connection Pools +---------------- + +For server-type type applications, that handle frequent requests and need +the database connection for a short period time while handling a request, +the use of a connection pool is recommended. asyncpg provides an advanced +pool implementation, which eliminates the need to use an external connection +pooler such as PgBouncer. + +To create a connection pool, use the +:func:`asyncpg.create_pool ` function. +The resulting :class:`Pool ` object can then be used +to borrow connections from the pool. + +Below is an example of how **asyncpg** can be used to implement a simple +Web service that computes the requested power of two. + + +.. code-block:: python + + import asyncio + import asyncpg + from aiohttp import web + + + async def handle(request): + """Handle incoming requests.""" + pool = request.app['pool'] + power = int(request.match_info.get('power', 10)) + + # Take a connection from the pool. + async with pool.acquire() as connection: + # Open a transaction. + async with connection.transaction(): + # Run the query passing the request argument. + result = await connection.fetchval('select 2 ^ $1', power) + return web.Response( + text="2 ^ {} is {}".format(power, result)) + + + async def init_app(): + """Initialize the application server.""" + app = web.Application() + # Create a database connection pool + app['pool'] = await asyncpg.create_pool(database='postgres', + user='postgres') + # Configure service routes + app.router.add_route('GET', '/{power:\d+}', handle) + app.router.add_route('GET', '/', handle) + return app + + + loop = asyncio.get_event_loop() + app = loop.run_until_complete(init_app()) + web.run_app(app) + +See :ref:`asyncpg-api-pool` API documentation for more information.