Skip to main content

SQL Query Library

Project description

DDSQL

pypi downloads versions codecov license

DDSQL is a Python library for building SQL queries with Jinja2 template rendering and database adapter support. Query results are automatically deserialized into typed models.

Installation

Install the library using pip:

pip install ddsql

Serializer

The serializer converts Python types to their SQL representations. You can use one of the built-in serializers (PostgresSerializer, ClickhouseSerializer) or create your own by inheriting from BaseSerializer.

Serialization Table

Python Type Base PostgreSQL ClickHouse
None NULL NULL NULL
bool true/false true/false true/false
int 123 123 123
float/Decimal 45.67 45.67 45.67
str 'value' 'value' 'value'
UUID '550e8400-...' '550e8400-...'::uuid toUUID('550e8400-...')
datetime '2025-01-01T12:00:00' '2025-01-01T12:00:00'::timestamp parseDateTimeBestEffort('2025-01-01T12:00:00')
date '2025-01-01' '2025-01-01'::date toDate('2025-01-01')
list/tuple/set (item1, item2, ...) (item1, item2, ...) (item1, item2, ...)

If you need to serialize a type not listed in the table, override the serialize_other_object method in your serializer:

from ddsql.serializers import BaseSerializer


class CustomSerializer(BaseSerializer):
    def serialize_other_object(self, value):
        if isinstance(value, CustomType):
            return ...
        ...

To serialize values in SQL templates, wrap parameters with serialize_value:

SELECT * 
FROM users
WHERE 
    name = {{ serialize_value(name) }}
    AND created_at > {{ serialize_value(created_at) }}

To add custom functions to templates, override the template_functions property:

from ddsql.serializers import BaseSerializer


class CustomSerializer(BaseSerializer):
    @property
    def template_functions(self):
        return {
            **super().template_functions,
            'some_function': ...,
        }

Adapter

Adapter encapsulates database interactions. To create an adapter, inherit from the Adapter base class and define two required elements:

  • serializer – an instance of a serializer for converting Python types to SQL representations;
  • _execute method – the database-specific query execution logic.
from ddsql.adapter import Adapter
from ddsql.serializers import PostgresSerializer


class PostgresAdapter(Adapter):
    serializer = PostgresSerializer()

    async def _execute(self) -> Sequence[Dict[str, Any]]:
        query = await self.get_query()  # get the rendered SQL query
        async with Atomic() as postgres_session:
            result = await postgres_session.execute(text(query))
            return [dict(zip(result.keys(), row)) for row in result.fetchall()]

SQLBase

SQLBase is configured once per project and defines which adapters are available for query execution. It serves as the central point that connects queries with database adapters.

Create a subclass with one or more adapters:

from ddsql.sqlbase import SQLBase
from ddsql.adapter import AdapterDescriptor


class SQL(SQLBase):
    postgres: PostgresAdapter = AdapterDescriptor(PostgresAdapter)
    clickhouse: ClickhouseAdapter = AdapterDescriptor(ClickhouseAdapter)

Execution example:

from ddsql.query import Query


query = Query(...)
result = await SQL(query=query).with_params(email='test@test.test', is_deleted=False).postgres.execute()

Query

Query knows where to get the template from and how to render a SQL query. It also handles result deserialization via the build_result method, which wraps raw database rows into the specified model (called internally by Adapter.execute).

Required parameters:

  • model – a declarative class (e.g., dataclass) describing the output result structure;
  • text or path – the SQL template source.

Inline Template (text)

from ddsql.query import Query


query = Query(
    model=User,
    text='SELECT user_id, name FROM users WHERE user_id = {{ serialize_value(user_id) }}'
)

File Template (path)

For file-based templates, set the SQL_TEMPLATES_DIR environment variable to the directory containing your SQL files:

export SQL_TEMPLATES_DIR=/app/src/templates/sql/

Then use a relative path:

from ddsql.query import Query


# Loads template from /app/src/templates/sql/users/get_by_id.sql
query = Query(
    model=User, 
    path='users/get_by_id.sql'
)

Result

The result of query execution is a Result object that wraps the data into the specified model:

  • get() – returns the first row as a model instance, or None if empty;
  • get_list() – returns all rows as a tuple of model instances;
  • rows – attribute for accessing raw data.

Complete Example

from dataclasses import dataclass
from datetime import datetime
from typing import Optional

from ddsql.query import Query
from ddsql.sqlbase import SQLBase
from ddsql.adapter import Adapter, AdapterDescriptor
from ddsql.serializers import PostgresSerializer


class PostgresAdapter(Adapter):
    serializer = PostgresSerializer()

    async def _execute(self):
        ...


class SQL(SQLBase):
    postgres: PostgresAdapter = AdapterDescriptor(PostgresAdapter)


@dataclass
class User:
    user_id: int
    name: str
    email: Optional[str]
    created_at: datetime
    is_deleted: bool


query = Query(
    model=User,
    text='''
        SELECT *
        FROM users
        WHERE 
            created_at > {{ serialize_value(created_after) }}
        LIMIT {{ limit }}
    '''
)


async def get_users():
    result = await (
        SQL(query=query)
        .with_params(created_after=datetime(2025, 1, 1))
        .with_params(limit=10)
        .postgres
        .execute()
    )
    return result.get_list()

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ddsql-0.0.2.tar.gz (6.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ddsql-0.0.2-py3-none-any.whl (9.3 kB view details)

Uploaded Python 3

File details

Details for the file ddsql-0.0.2.tar.gz.

File metadata

  • Download URL: ddsql-0.0.2.tar.gz
  • Upload date:
  • Size: 6.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for ddsql-0.0.2.tar.gz
Algorithm Hash digest
SHA256 94d16d41615676493f2a9a8b0753c5f99dee86cdc46d29a2b2d6ed549a95d1a2
MD5 bb1f9b769fc74d12d24d2df5900ac025
BLAKE2b-256 5a2e3ad78b9dbeecbc25e9aa414d11ef920e78acbf5dd25c9bf8e4e65a7ba194

See more details on using hashes here.

Provenance

The following attestation bundles were made for ddsql-0.0.2.tar.gz:

Publisher: publish_python_package.yml on davyddd/ddsql

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file ddsql-0.0.2-py3-none-any.whl.

File metadata

  • Download URL: ddsql-0.0.2-py3-none-any.whl
  • Upload date:
  • Size: 9.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for ddsql-0.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 1286b29c1b9b832624cc3af4d2a02875d4808a4171afd47e9d14613f5a41867a
MD5 237c115a37182b2d00f70f91ca1c4f76
BLAKE2b-256 b80f6bfcbe3f4c2a980c7f7763c57ecead26f598b18f553f59d173fdf2190bc0

See more details on using hashes here.

Provenance

The following attestation bundles were made for ddsql-0.0.2-py3-none-any.whl:

Publisher: publish_python_package.yml on davyddd/ddsql

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page