# Python

**gel-python** is the official Gel driver for Python. It provides both [blocking IO](https://docs.geldata.com/reference/using/python/client.md#gel-python-blocking-api-reference) and [asyncio](https://docs.geldata.com/reference/using/python/client.md#gel-python-asyncio-api-reference) implementations.

## Installation

Install the [client package from PyPI](https://pypi.org/project/gel/) using your package manager of choice.

- pip:
  ```bash
  $ pip install gel
  ```
- uv:
  ```bash
  $ uv add gel
  ```

## Basic Usage

To start using Gel in Python, create an [`gel.Client`](https://docs.geldata.com/reference/using/python/client.md#gel.Client) instance using [`gel.create_client()`](https://docs.geldata.com/reference/using/python/client.md#gel.create_client) or [`gel.create_async_client()`](https://docs.geldata.com/reference/using/python/client.md#gel.create_async_client) for AsyncIO. This client instance manages a pool of connections to the database which it discovers automatically from either being in a `gel project init` directory or being provided connection details via Environment Variables. See [the environment section of the connection reference](https://docs.geldata.com/reference/using/connection.md#ref-reference-connection-environments) for more details and options.

> Note: If you're using Gel Cloud to host your development instance, you can use the `gel cloud login` command to authenticate with Gel Cloud and then use the `gel project init --server-instance <instance-name>` command to create a local project-linked instance that is linked to an Gel Cloud instance. For more details, see [the Gel Cloud guide](https://docs.geldata.com/cloud.md#ref-guide-cloud).

- Blocking:
  ```python
  import datetime
  import gel
  
  client = gel.create_client()
  
  client.query("""
      INSERT User {
          name := <str>$name,
          dob := <cal::local_date>$dob
      }
  """, name="Bob", dob=datetime.date(1984, 3, 1))
  
  user_set = client.query(
      "SELECT User {name, dob} FILTER .name = <str>$name", name="Bob")
  # *user_set* now contains
  # Set{Object{name := 'Bob', dob := datetime.date(1984, 3, 1)}}
  
  client.close()
  ```
- AsyncIO:
  ```python
  import asyncio
  import datetime
  import gel
  
  client = gel.create_async_client()
  
  async def main():
      await client.query("""
          INSERT User {
              name := <str>$name,
              dob := <cal::local_date>$dob
          }
      """, name="Bob", dob=datetime.date(1984, 3, 1))
  
      user_set = await client.query(
          "SELECT User {name, dob} FILTER .name = <str>$name", name="Bob")
      # *user_set* now contains
      # Set{Object{name := 'Bob', dob := datetime.date(1984, 3, 1)}}
  
      await client.aclose()
  
  asyncio.run(main())
  ```

### Client connection pools

For server-type applications that handle frequent requests and need the database connection for a short period of time while handling a request, you will want to use a connection pool. Both [`gel.Client`](https://docs.geldata.com/reference/using/python/client.md#gel.Client) and [`gel.AsyncIOClient`](https://docs.geldata.com/reference/using/python/client.md#gel.AsyncIOClient) come with such a pool.

For [`gel.Client`](https://docs.geldata.com/reference/using/python/client.md#gel.Client), all methods are thread-safe. You can share the same client instance safely across multiple threads, and run queries concurrently. Likewise, [`AsyncIOClient`](https://docs.geldata.com/reference/using/python/client.md#gel.AsyncIOClient) is designed to be shared among different [`asyncio.Task`](https://docs.python.org/3/library/asyncio-task.html#asyncio.Task)/coroutines for concurrency.

Below is an example of a web API server running [fastapi](https://fastapi.tiangolo.com/):

```python
import asyncio
import gel
from fastapi import FastAPI, Query

app = FastAPI()

@app.on_event("startup")
async def startup_event():
    """Initialize the database client on startup."""
    app.state.client = gel.create_async_client()
    # Optional: explicitly start up the connection pool
    await app.state.client.ensure_connected()

@app.get("/users")
async def handle(
  name: str = Query(None)
):
    """Handle incoming requests."""
    client = app.state.client

    # Execute the query on any pool connection
    if name:
      result = await client.query_single(
          '''
              SELECT User {first_name, email, bio}
              FILTER .name = <str>$username
          ''', username=name)
    else:
      result = await client.query(
          '''
              SELECT User {first_name, email, bio}
          ''')
    return result

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)
```

Note that the client instance itself is created synchronously. Pool connections are created lazily as they are needed. If you want to explicitly connect to the database in `startup_event()`, use the `ensure_connected()` method on the client.

For more information, see API documentation of [the blocking client](https://docs.geldata.com/reference/using/python/client.md#gel-python-blocking-api-client) and [the asynchronous client](https://docs.geldata.com/reference/using/python/client.md#gel-python-async-api-client).

### Transactions

To create a [transaction](https://docs.geldata.com/reference/edgeql/transactions.md#ref-eql-transactions) use the `transaction()` method on the client instance:

- [`AsyncIOClient.transaction()`](https://docs.geldata.com/reference/using/python/client.md#gel.AsyncIOClient.transaction)
- [`Client.transaction()`](https://docs.geldata.com/reference/using/python/client.md#gel.Client.transaction)

Example:

- Blocking:
  ```python
  for tx in client.transaction():
      with tx:
          tx.execute("INSERT User {name := 'Don'}")
  ```
- AsyncIO:
  ```python
  async for tx in client.transaction():
      async with tx:
          await tx.execute("INSERT User {name := 'Don'}")
  ```

> Note: When not in an explicit transaction block, any changes to the database will be applied immediately.

For more information, see API documentation of transactions for [the blocking client](https://docs.geldata.com/reference/using/python/client.md#gel-python-blocking-api-transaction) and [the AsyncIO client](https://docs.geldata.com/reference/using/python/client.md#gel-python-asyncio-api-transaction).

## Code generation

The `gel-python` package exposes a command-line tool to generate typesafe functions from `*.edgeql` files, using [`dataclasses`](https://docs.python.org/3/library/dataclasses.html#module-dataclasses) for objects primarily.

- queries/get_user_by_name.edgeql:
  ```edgeql
  with
      name := <str>$name,
  select User { first_name, email, bio }
  filter .name = name;
  ```

```bash
$ gel-py
# or
$ python -m gel.codegen
```

```python
import gel
from .queries import get_user_by_name_sync_edgeql as get_user_by_name_qry

client = gel.create_async_client()

async def main():
  result = await get_user_by_name_qry.get_user_by_name(client, name="John")
  print(result)

asyncio.run(main())
```

