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.1.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.1-py3-none-any.whl (9.3 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: ddsql-0.0.1.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.1.tar.gz
Algorithm Hash digest
SHA256 406658529f884c1ee8d7b2321991330e934f7014f2c1862a1ab2939e44243742
MD5 31c6e2d7da0de123837ba079b6461230
BLAKE2b-256 b84e2f061cbd2333955a01d2133da3a191bf7d7cc5b0aba98076110c60c92f0d

See more details on using hashes here.

Provenance

The following attestation bundles were made for ddsql-0.0.1.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.1-py3-none-any.whl.

File metadata

  • Download URL: ddsql-0.0.1-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.1-py3-none-any.whl
Algorithm Hash digest
SHA256 29f3609948fa57aecc9841bccef96a948ed6cc7049a6796a65739943074d2daa
MD5 960066223e00c73f4e16a5b3511e08af
BLAKE2b-256 e5fdf08b97e170387ce65415faaebc7d3ac743b0148d3df93134be36dba49ddf

See more details on using hashes here.

Provenance

The following attestation bundles were made for ddsql-0.0.1-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