sqlcraft


Namesqlcraft JSON
Version 0.1.3 PyPI version JSON
download
home_page
SummaryEasy way to write SQL queries
upload_time2022-12-19 13:29:39
maintainer
docs_urlNone
author
requires_python>=3.7
licenseMIT License Copyright (c) 2022 Dmitry Kononenko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
keywords sql query builder postgresql
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            [![On-commit](https://github.com/Dmitry-k42/flow-sql/actions/workflows/on-commit.yml/badge.svg)](https://github.com/Dmitry-k42/flow-sql/actions/workflows/on-commit.yml)

# Build SQL queries fluently

`sqlcraft` is a Python package for constructing SQL queries with [fluent interface](https://en.wikipedia.org/wiki/Method_chaining).

**Important**: Currently the package supports PostgreSQL database **only**.

## Installation

```shell
pip3 install sqlcraft
```

# SELECT

Here is a simple query:
```python
import psycopg2
from sqlcraft import Query

conn = psycopg2.connect(<put your connection credentials here>)
cmd = (
    Query(conn)
    .select(['id', 'name', 'age'])
    .from_('users')
    .where({
        'id': 42,
        'name': 'Jane',
    })
    .order('id')
)
```
Get resulting query text with `cmd.as_string()` method:
```SQL
SELECT "id", "name", "age" FROM "users" WHERE ("id" = 42) AND ("name" = 'Jane') ORDER BY "id"
```

Surely all the values will be passed as query parameters in order to prevent
[SQL injection](https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=&cad=rja&uact=8&ved=2ahUKEwiu3OPy7sT7AhXNSPEDHUuhC88QFnoECEcQAQ&url=https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FSQL_injection&usg=AOvVaw1uzN238Ma4U7MFZg35w2eA).
Here the resulting query is illustrated with values inside for better understanding.

## Data fetching
When you have built the query, use one of methods `.all()`, `column()`, `.one()`
or `.scalar()`. Method `.all()` allows you iterate over all the resulting rows from the database.
By default it returns a generator. Set parameter `eager=True` to get all the rows as an array.
Use `.column()` to fetch only a single column, `.one()` to fetch a single row and `.scalar()`
to fetch just one value. Example:
```python
for row in cmd.all():
    id, name, age = row
```
Get count of rows in the table:
```python
users_total = Query(conn).select('count(*)').from_('users').scalar()
```

## Chaining conditions

Using method chaining you can easily combine different conditions in the WHERE clause.
Some examples:

```python
Query(conn)
    ...
    .and_where({'gender': ['male', 'female']})
    .and_where('active')
    .and_where('NOT banned')
    .and_where(['BETWEEN', 'age', 18, 25])
    .and_where(['!=', 'name', 'Aaron'])
    .and_where('credit = debit')
    ...
```
This code will produce
```SQL
WHERE ("gender" IN ('male', 'female')) AND ("active") AND ("age" BETWEEN 18 AND 25) AND ("name" != 'Aaron') AND ("credit" = "debit")
```

You can easily combine AND'n'OR conditions:
```python
Query(conn)
    .where([
        'OR',
        [
            'AND',
            ['>', 'age', 35],
            ['!=', 'name', 'Donald'],
        ],
        [
            'AND',
            ['<', 'age', 18],
            'NOT active',
        ],
    ])
```
Result:
```SQL
WHERE (("age" > 35) AND ("name" != 'Donald') OR (("age" < 18) AND (NOT "active"))
```

Quoting is on by default. Builder will surround every field or table name with `"` (for PostgreSQL)
if the string doesn't contain `(` and/or `)` symbols. If you want to turn off quoting manually,
just embrace identifier with brackets. Examples:
```python
Query(conn)
    ...
    .and_where('price > 10')         # "price" > 10       <- Quoting in on by default
    .and_where('(no_quoting) > 10')  # (no_quoting) > 10  <- Quoting turned off manually
    .and_where('sum("price") > 10')  # sum("price") > 10  <- You should do quoting here manually!
    ...
```

### Joins
Table joining are provided with methods `.join()`, `.join_left()`, `.join_right()`,
`.join_inner()` (synonim to `.join()`) and `.join_full()`. Example:
```python
Query(conn)
    .select(['users.id', 'wallets.balance', 'l.lat', 'l.lon'])
    .from_('users')
    .join('wallets', on='users.id=wallets.user_id')
    .join_left(
        'location',
        alias='l',  # Table aliasing is available with `alias` argument
        on='users.id=l.user_id'
    )
```

### Subqueries
You can combine queries together when you need to have more complicated queries:
```python
subq = Query(conn).select('*').from_('devices').where('active')

# Subquery in FROM clause
(
    Query(conn)
    .select(['d.id', 'd.imei'])
    .from_(subq, alias='d')
)

# Use CTE in WITH and JOIN clauses
(
    Query(conn)
    .with_(subq, alias='sq')
    ...
    .from_('user', alias='u')
    .join_('sq', on='u.id=sq.user_id')
    ...
)

# Subquery in WHERE clause
(
    Query(conn)
    ...
    .and_where([
        'EXISTS',
        'device_id',
        Query(conn).select('id').from_('devices')
    ])
    ...
)
```

# INSERT
One statement usage:
```python
from sqlcraft import Insert

Insert(
    conn,
    table='users',
    values=[
        {'id': 42, 'name': 'Viktor', 'weight': 75.27, 'active': True, 'pet': None},
        {'id': 43, 'name': 'Anna', 'weight': 56.76, 'active': False, 'pet': 'kitty'},
    ]
).execute()
```

Another syntax:
```python
Insert(
    conn,
    table='users',
    columns=['id', 'name', 'weight', 'active', 'pet'],
    values=[
        [42, 'Viktor', 75.27, True, None],
        [43, 'Anna', 56.76, False, 'kitty'],
    ]
).execute()
```

Fluent syntax:
```python
(
    Insert(conn)
    .table('users')
    .columns(['id', 'name', 'weight', 'active', 'pet'])
    .add_values([42, 'Viktor', 75.27, True, None])
    .add_values([43, 'Anna', 56.76, False, 'kitty'])
    .execute()
)
```

RETURNING clause is available:
```python
cmd = Insert(
    conn,
    table='users',
    values={'name': 'Viktor'},
)
viktor_id = cmd.returning('id').scalar()
```

PostgreSQL extends standard syntax for `INSERT` command. It allows to handle conflicts with special
`ON CONFLICT` keyword. For more information, please see [here](https://www.postgresql.org/docs/current/sql-insert.html)
("ON CONFLICT Clause" section). To create this clause you can use `.on_conflict_do_nothing()`
and `.on_conflict_do_update()` methods.
```python
from sqlcraft import Expr

(
    # If another row in the table with id 42 already exists,
    # a new row will be ignored
    Insert(conn)
    .table('users')
    .columns(['id', 'name'])
    .add_values([42, 'Viktor'])
    .on_conflict_do_nothing('id')
    .execute()
)

(
    # If another row in the table with id 42 already exists,
    # then `name` column of existing row will be updated with a new value
    Insert(conn)
    .table('users')
    .columns(['id', 'name'])
    .add_values([42, 'Viktor'])
    .on_conflict_do_update(
        'id',
        {
            'name': Expr('EXCLUDED.name'),
        },
    )
    .execute()
)
```
Please take a look at `Expr` class. It tells the builder not to quote and not to parametrize
its content. This is a useful tool when you want to pass a part of SQL query as is. You should know
what you are doing with `Expr` because burden of protection against SQL injections lays on
your shoulders. **NEVER use potentially unsafe data as an argument of `Expr`!** In this case
we know what we are doing. Without `Expr` running this command could replace
`name` field with a string "EXCLUDED.name" instead of "Viktor". Parametrizing works always by
default. This is surely good but actually not a thing we want to get in this case. This is why we wrap "EXCLUDED.name"
value with calling `Expr` in order to tell the builder that "EXCLUDED.name" is a part of the query.

It is possible to use `INSERT...SELECT` construction - just pass pre-built `Query` object as an
argument:
```python
cmd = Query(conn).select('id, name, gender').from_('users_src')
Insert(conn, table='users_dst', values=cmd).execute()
```
Resulting query:
```SQL
INSERT INTO "users_dst" SELECT "id", "name", "gender" FROM "users_src"
```

# UPDATE
Simple syntax:
```python
Update(
    conn,
    table='users',
    fields={
        'name': 'New name',
    },
    where={
        'id': 1,
    },
).execute()
```

Fluent syntax:
```python
(
    Update(conn)
    .table('users')
    .set({'name': 'New name'})
    .add_set('age', Expr('age + 1'))
    .where({'id': 1})
    .and_where(['!=', 'name', 'Johnny'])
    .returning('id')
    .column(eager=True)
)
```

# DELETE

Simple syntax:
```python
from sqlcraft import Delete

Delete(
    conn,
    from_='users',
    where={'id': 1},
).execute()
```

The same using method chaining:
```python
(
    Delete(conn)
    .from_('users')
    .where({'id': 1})
    .execute()
)
```

Note that you can not execute `DELETE` queries without specified `WHERE` condition.
The builder raises an exception:

```Exception: Sorry empty WHERE block is restricted on DELETE operations for security reasons```

To remove all rows from the table, you should explicitly call `where` method like this:
```python
(
    Delete(conn)
    .from_('users')
    .where(True)  # The way how to tell the builder that deleting all the rows is ok
    .execute()
)
```

# COPY

`COPY` command is a good choise for bulk insert lots of rows into a table. Here is how to use
it with `sqlcraft`:
```python
from sqlcraft import Copy

Copy(
    conn,
    table='users',
    columns=('id', 'name', 'age'),
    rows=[
        (1, 'Chris', 37),
        (2, 'Brenda', 43),
        (3, 'Mary', 22),
    ]
).execute()
```

# Raw queries

Above the common most-used queries were described. But sometimes we face unusual queries.
It is possible to call raw queries with `Command` class:

```python
from sqlcraft import Command

Command(
    conn,
    'ALTER TABLE "users" DROP COLUMN "gender"'
).execute()
```

Use `%(param_name)s` syntax for passing parameters to your query.
Example of using parametrization with `Command`:
```python
(
    Command(
        conn,
        'SELECT * FROM "users" WHERE "id" = %(id)s'
    )
    .params({
        'id': 1,
    })
    .execute()
)
```

# Advanced PostgreSQL connection

This package provides a custom `Connection` class which replaces
`psycopg2` connection. You can use it if set argument `connection_factory`
this way:
```python
import psycopg2
from sqlcraft.conn import Connection

conn = psycopg2.connect(
    <put your connection credentials here>,
    connection_factory=Connection,
)
```

`Connection` has a few advantages over the standard `psycopg2` connection:

  * **Multi-layered transactions**

    It is available to enclose a transaction into another one. The level of enclosed
    transactions is limited only by your database.

    ```python
    # Open the first transaction
    conn.begin_transaction()
    # Do something here. This will be commited
    ...
    # Open the inner transaction
    conn.begin_transaction()
    # Do database calls. These calls will be rolled back
    ...
    # The inner transaction canceled
    conn.rollback()
    # Do some calls again. This will be commited
    ...
    # Commit the first transaction
    conn.commit()
    ```

  * **Dict-like fetched data**

    With `Connection` class method `.all()` returns data rows has type `DBRow`.
    The `DBRow` implements a few more ways to access the resulting data. Example:
    ```python
    cmd = Query(conn).select('id, name').from_('users')
    for row in cmd:
        # row is a tuple-like object
        id, name = row
        # row is a dict-like object
        id = row['id']
        name = row['name']
        # access to the values via properties
        row.id
        row.name
    ```

            

Raw data

            {
    "_id": null,
    "home_page": "",
    "name": "sqlcraft",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.7",
    "maintainer_email": "",
    "keywords": "sql,query builder,PostgreSQL",
    "author": "",
    "author_email": "Dmitrii Kononenko <dmitriy.sergeevitch@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/6f/ff/5e9980e892c7132360e35eabe3cd1885c0eb0c3ad5fecd9aa53e97562d0f/sqlcraft-0.1.3.tar.gz",
    "platform": null,
    "description": "[![On-commit](https://github.com/Dmitry-k42/flow-sql/actions/workflows/on-commit.yml/badge.svg)](https://github.com/Dmitry-k42/flow-sql/actions/workflows/on-commit.yml)\n\n# Build SQL queries fluently\n\n`sqlcraft` is a Python package for constructing SQL queries with [fluent interface](https://en.wikipedia.org/wiki/Method_chaining).\n\n**Important**: Currently the package supports PostgreSQL database **only**.\n\n## Installation\n\n```shell\npip3 install sqlcraft\n```\n\n# SELECT\n\nHere is a simple query:\n```python\nimport psycopg2\nfrom sqlcraft import Query\n\nconn = psycopg2.connect(<put your connection credentials here>)\ncmd = (\n    Query(conn)\n    .select(['id', 'name', 'age'])\n    .from_('users')\n    .where({\n        'id': 42,\n        'name': 'Jane',\n    })\n    .order('id')\n)\n```\nGet resulting query text with `cmd.as_string()` method:\n```SQL\nSELECT \"id\", \"name\", \"age\" FROM \"users\" WHERE (\"id\" = 42) AND (\"name\" = 'Jane') ORDER BY \"id\"\n```\n\nSurely all the values will be passed as query parameters in order to prevent\n[SQL injection](https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=&cad=rja&uact=8&ved=2ahUKEwiu3OPy7sT7AhXNSPEDHUuhC88QFnoECEcQAQ&url=https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FSQL_injection&usg=AOvVaw1uzN238Ma4U7MFZg35w2eA).\nHere the resulting query is illustrated with values inside for better understanding.\n\n## Data fetching\nWhen you have built the query, use one of methods `.all()`, `column()`, `.one()`\nor `.scalar()`. Method `.all()` allows you iterate over all the resulting rows from the database.\nBy default it returns a generator. Set parameter `eager=True` to get all the rows as an array.\nUse `.column()` to fetch only a single column, `.one()` to fetch a single row and `.scalar()`\nto fetch just one value. Example:\n```python\nfor row in cmd.all():\n    id, name, age = row\n```\nGet count of rows in the table:\n```python\nusers_total = Query(conn).select('count(*)').from_('users').scalar()\n```\n\n## Chaining conditions\n\nUsing method chaining you can easily combine different conditions in the WHERE clause.\nSome examples:\n\n```python\nQuery(conn)\n    ...\n    .and_where({'gender': ['male', 'female']})\n    .and_where('active')\n    .and_where('NOT banned')\n    .and_where(['BETWEEN', 'age', 18, 25])\n    .and_where(['!=', 'name', 'Aaron'])\n    .and_where('credit = debit')\n    ...\n```\nThis code will produce\n```SQL\nWHERE (\"gender\" IN ('male', 'female')) AND (\"active\") AND (\"age\" BETWEEN 18 AND 25) AND (\"name\" != 'Aaron') AND (\"credit\" = \"debit\")\n```\n\nYou can easily combine AND'n'OR conditions:\n```python\nQuery(conn)\n    .where([\n        'OR',\n        [\n            'AND',\n            ['>', 'age', 35],\n            ['!=', 'name', 'Donald'],\n        ],\n        [\n            'AND',\n            ['<', 'age', 18],\n            'NOT active',\n        ],\n    ])\n```\nResult:\n```SQL\nWHERE ((\"age\" > 35) AND (\"name\" != 'Donald') OR ((\"age\" < 18) AND (NOT \"active\"))\n```\n\nQuoting is on by default. Builder will surround every field or table name with `\"` (for PostgreSQL)\nif the string doesn't contain `(` and/or `)` symbols. If you want to turn off quoting manually,\njust embrace identifier with brackets. Examples:\n```python\nQuery(conn)\n    ...\n    .and_where('price > 10')         # \"price\" > 10       <- Quoting in on by default\n    .and_where('(no_quoting) > 10')  # (no_quoting) > 10  <- Quoting turned off manually\n    .and_where('sum(\"price\") > 10')  # sum(\"price\") > 10  <- You should do quoting here manually!\n    ...\n```\n\n### Joins\nTable joining are provided with methods `.join()`, `.join_left()`, `.join_right()`,\n`.join_inner()` (synonim to `.join()`) and `.join_full()`. Example:\n```python\nQuery(conn)\n    .select(['users.id', 'wallets.balance', 'l.lat', 'l.lon'])\n    .from_('users')\n    .join('wallets', on='users.id=wallets.user_id')\n    .join_left(\n        'location',\n        alias='l',  # Table aliasing is available with `alias` argument\n        on='users.id=l.user_id'\n    )\n```\n\n### Subqueries\nYou can combine queries together when you need to have more complicated queries:\n```python\nsubq = Query(conn).select('*').from_('devices').where('active')\n\n# Subquery in FROM clause\n(\n    Query(conn)\n    .select(['d.id', 'd.imei'])\n    .from_(subq, alias='d')\n)\n\n# Use CTE in WITH and JOIN clauses\n(\n    Query(conn)\n    .with_(subq, alias='sq')\n    ...\n    .from_('user', alias='u')\n    .join_('sq', on='u.id=sq.user_id')\n    ...\n)\n\n# Subquery in WHERE clause\n(\n    Query(conn)\n    ...\n    .and_where([\n        'EXISTS',\n        'device_id',\n        Query(conn).select('id').from_('devices')\n    ])\n    ...\n)\n```\n\n# INSERT\nOne statement usage:\n```python\nfrom sqlcraft import Insert\n\nInsert(\n    conn,\n    table='users',\n    values=[\n        {'id': 42, 'name': 'Viktor', 'weight': 75.27, 'active': True, 'pet': None},\n        {'id': 43, 'name': 'Anna', 'weight': 56.76, 'active': False, 'pet': 'kitty'},\n    ]\n).execute()\n```\n\nAnother syntax:\n```python\nInsert(\n    conn,\n    table='users',\n    columns=['id', 'name', 'weight', 'active', 'pet'],\n    values=[\n        [42, 'Viktor', 75.27, True, None],\n        [43, 'Anna', 56.76, False, 'kitty'],\n    ]\n).execute()\n```\n\nFluent syntax:\n```python\n(\n    Insert(conn)\n    .table('users')\n    .columns(['id', 'name', 'weight', 'active', 'pet'])\n    .add_values([42, 'Viktor', 75.27, True, None])\n    .add_values([43, 'Anna', 56.76, False, 'kitty'])\n    .execute()\n)\n```\n\nRETURNING clause is available:\n```python\ncmd = Insert(\n    conn,\n    table='users',\n    values={'name': 'Viktor'},\n)\nviktor_id = cmd.returning('id').scalar()\n```\n\nPostgreSQL extends standard syntax for `INSERT` command. It allows to handle conflicts with special\n`ON CONFLICT` keyword. For more information, please see [here](https://www.postgresql.org/docs/current/sql-insert.html)\n(\"ON CONFLICT Clause\" section). To create this clause you can use `.on_conflict_do_nothing()`\nand `.on_conflict_do_update()` methods.\n```python\nfrom sqlcraft import Expr\n\n(\n    # If another row in the table with id 42 already exists,\n    # a new row will be ignored\n    Insert(conn)\n    .table('users')\n    .columns(['id', 'name'])\n    .add_values([42, 'Viktor'])\n    .on_conflict_do_nothing('id')\n    .execute()\n)\n\n(\n    # If another row in the table with id 42 already exists,\n    # then `name` column of existing row will be updated with a new value\n    Insert(conn)\n    .table('users')\n    .columns(['id', 'name'])\n    .add_values([42, 'Viktor'])\n    .on_conflict_do_update(\n        'id',\n        {\n            'name': Expr('EXCLUDED.name'),\n        },\n    )\n    .execute()\n)\n```\nPlease take a look at `Expr` class. It tells the builder not to quote and not to parametrize\nits content. This is a useful tool when you want to pass a part of SQL query as is. You should know\nwhat you are doing with `Expr` because burden of protection against SQL injections lays on\nyour shoulders. **NEVER use potentially unsafe data as an argument of `Expr`!** In this case\nwe know what we are doing. Without `Expr` running this command could replace\n`name` field with a string \"EXCLUDED.name\" instead of \"Viktor\". Parametrizing works always by\ndefault. This is surely good but actually not a thing we want to get in this case. This is why we wrap \"EXCLUDED.name\"\nvalue with calling `Expr` in order to tell the builder that \"EXCLUDED.name\" is a part of the query.\n\nIt is possible to use `INSERT...SELECT` construction - just pass pre-built `Query` object as an\nargument:\n```python\ncmd = Query(conn).select('id, name, gender').from_('users_src')\nInsert(conn, table='users_dst', values=cmd).execute()\n```\nResulting query:\n```SQL\nINSERT INTO \"users_dst\" SELECT \"id\", \"name\", \"gender\" FROM \"users_src\"\n```\n\n# UPDATE\nSimple syntax:\n```python\nUpdate(\n    conn,\n    table='users',\n    fields={\n        'name': 'New name',\n    },\n    where={\n        'id': 1,\n    },\n).execute()\n```\n\nFluent syntax:\n```python\n(\n    Update(conn)\n    .table('users')\n    .set({'name': 'New name'})\n    .add_set('age', Expr('age + 1'))\n    .where({'id': 1})\n    .and_where(['!=', 'name', 'Johnny'])\n    .returning('id')\n    .column(eager=True)\n)\n```\n\n# DELETE\n\nSimple syntax:\n```python\nfrom sqlcraft import Delete\n\nDelete(\n    conn,\n    from_='users',\n    where={'id': 1},\n).execute()\n```\n\nThe same using method chaining:\n```python\n(\n    Delete(conn)\n    .from_('users')\n    .where({'id': 1})\n    .execute()\n)\n```\n\nNote that you can not execute `DELETE` queries without specified `WHERE` condition.\nThe builder raises an exception:\n\n```Exception: Sorry empty WHERE block is restricted on DELETE operations for security reasons```\n\nTo remove all rows from the table, you should explicitly call `where` method like this:\n```python\n(\n    Delete(conn)\n    .from_('users')\n    .where(True)  # The way how to tell the builder that deleting all the rows is ok\n    .execute()\n)\n```\n\n# COPY\n\n`COPY` command is a good choise for bulk insert lots of rows into a table. Here is how to use\nit with `sqlcraft`:\n```python\nfrom sqlcraft import Copy\n\nCopy(\n    conn,\n    table='users',\n    columns=('id', 'name', 'age'),\n    rows=[\n        (1, 'Chris', 37),\n        (2, 'Brenda', 43),\n        (3, 'Mary', 22),\n    ]\n).execute()\n```\n\n# Raw queries\n\nAbove the common most-used queries were described. But sometimes we face unusual queries.\nIt is possible to call raw queries with `Command` class:\n\n```python\nfrom sqlcraft import Command\n\nCommand(\n    conn,\n    'ALTER TABLE \"users\" DROP COLUMN \"gender\"'\n).execute()\n```\n\nUse `%(param_name)s` syntax for passing parameters to your query.\nExample of using parametrization with `Command`:\n```python\n(\n    Command(\n        conn,\n        'SELECT * FROM \"users\" WHERE \"id\" = %(id)s'\n    )\n    .params({\n        'id': 1,\n    })\n    .execute()\n)\n```\n\n# Advanced PostgreSQL connection\n\nThis package provides a custom `Connection` class which replaces\n`psycopg2` connection. You can use it if set argument `connection_factory`\nthis way:\n```python\nimport psycopg2\nfrom sqlcraft.conn import Connection\n\nconn = psycopg2.connect(\n    <put your connection credentials here>,\n    connection_factory=Connection,\n)\n```\n\n`Connection` has a few advantages over the standard `psycopg2` connection:\n\n  * **Multi-layered transactions**\n\n    It is available to enclose a transaction into another one. The level of enclosed\n    transactions is limited only by your database.\n\n    ```python\n    # Open the first transaction\n    conn.begin_transaction()\n    # Do something here. This will be commited\n    ...\n    # Open the inner transaction\n    conn.begin_transaction()\n    # Do database calls. These calls will be rolled back\n    ...\n    # The inner transaction canceled\n    conn.rollback()\n    # Do some calls again. This will be commited\n    ...\n    # Commit the first transaction\n    conn.commit()\n    ```\n\n  * **Dict-like fetched data**\n\n    With `Connection` class method `.all()` returns data rows has type `DBRow`.\n    The `DBRow` implements a few more ways to access the resulting data. Example:\n    ```python\n    cmd = Query(conn).select('id, name').from_('users')\n    for row in cmd:\n        # row is a tuple-like object\n        id, name = row\n        # row is a dict-like object\n        id = row['id']\n        name = row['name']\n        # access to the values via properties\n        row.id\n        row.name\n    ```\n",
    "bugtrack_url": null,
    "license": "MIT License  Copyright (c) 2022 Dmitry Kononenko  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ",
    "summary": "Easy way to write SQL queries",
    "version": "0.1.3",
    "split_keywords": [
        "sql",
        "query builder",
        "postgresql"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "md5": "924d2ecb1c87a4b4bc4a8a747e52c030",
                "sha256": "4708c4e6ae92cba282de58d1d01d73d73e86452ff0a961379a51acc06f84965b"
            },
            "downloads": -1,
            "filename": "sqlcraft-0.1.3-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "924d2ecb1c87a4b4bc4a8a747e52c030",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.7",
            "size": 27414,
            "upload_time": "2022-12-19T13:29:36",
            "upload_time_iso_8601": "2022-12-19T13:29:36.878642Z",
            "url": "https://files.pythonhosted.org/packages/6f/82/e3549bd6e06c417c3319922497fbc9c137210b7a7b05b92974718a048549/sqlcraft-0.1.3-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "md5": "bf9b8c2c3a0a676b9def7d6d1dd6b03b",
                "sha256": "8c960275a1fb1a2ca05b437eb323270ceffc6e0726365b54c413db7c90bc25f1"
            },
            "downloads": -1,
            "filename": "sqlcraft-0.1.3.tar.gz",
            "has_sig": false,
            "md5_digest": "bf9b8c2c3a0a676b9def7d6d1dd6b03b",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.7",
            "size": 27319,
            "upload_time": "2022-12-19T13:29:39",
            "upload_time_iso_8601": "2022-12-19T13:29:39.517556Z",
            "url": "https://files.pythonhosted.org/packages/6f/ff/5e9980e892c7132360e35eabe3cd1885c0eb0c3ad5fecd9aa53e97562d0f/sqlcraft-0.1.3.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2022-12-19 13:29:39",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "lcname": "sqlcraft"
}
        
Elapsed time: 0.06447s