Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Lint

on:
pull_request:
push:
branches:
- main

env:
UV_VERSION: "0.7.13"

jobs:
check:
name: Style-check ${{ matrix.python-version }}
runs-on: ubuntu-latest
strategy:
matrix:
python-version:
- "3.9"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might keep 3.10 and remove 3.9 here. Due to end-of-life timelines and such.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heard. I relaxed because it was causing a dep graph issue since redisvl allows for lower version now

- "3.11"
- "3.13"

steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Install Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install uv
uses: astral-sh/setup-uv@v4
with:
version: ${{ env.UV_VERSION }}
enable-cache: true
python-version: ${{ matrix.python-version }}
cache-dependency-glob: |
pyproject.toml
uv.lock

- name: Install dependencies
run: |
uv sync --frozen

- name: check-sort-import
run: |
make check-sort-imports

- name: check-black-format
run: |
make check-format

- name: check-mypy
run: |
make check-types

48 changes: 48 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Test Suite

on:
pull_request:
push:
branches:
- main
workflow_dispatch:

env:
UV_VERSION: "0.7.13"

jobs:
test:
name: Python ${{ matrix.python-version }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, would remove 3.9.


steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Install Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install uv
uses: astral-sh/setup-uv@v4
with:
version: ${{ env.UV_VERSION }}
enable-cache: true
python-version: ${{ matrix.python-version }}
cache-dependency-glob: |
pyproject.toml
uv.lock

- name: Install dependencies
run: |
uv sync

- name: Run tests
run: |
make test

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ wheels/
*.egg-info/
.installed.cfg
*.egg
.python-version

# Virtual environments
.venv/
Expand Down
17 changes: 17 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
repos:
- repo: local
hooks:
- id: code-quality-checks
name: Run pre-commit checks (format, sort-imports, check-mypy)
entry: bash -c 'make format && make check-sort-imports && make check-types'
language: system
pass_filenames: false
- repo: https://github.com/codespell-project/codespell
rev: v2.2.6
hooks:
- id: codespell
name: Check spelling
args:
- --write-changes
- --skip=*.pyc,*.pyo,*.lock,*.git,*.mypy_cache,__pycache__,*.egg-info,.pytest_cache,env,venv,.venv

67 changes: 67 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
.PHONY: install format lint test clean check-types check-format check-sort-imports sort-imports build help
.DEFAULT_GOAL := help

# Allow passing arguments to make targets (e.g., make test ARGS="...")
ARGS ?=

install: ## Install the project and all dependencies
@echo "🚀 Installing project dependencies with uv"
uv sync

format: ## Format code with isort and black
@echo "🎨 Formatting code"
uv run isort ./sql_redis ./tests/ --profile black
uv run black ./sql_redis ./tests/

check-format: ## Check code formatting
@echo "🔍 Checking code formatting"
uv run black --check ./sql_redis ./tests/

sort-imports: ## Sort imports with isort
@echo "📦 Sorting imports"
uv run isort ./sql_redis ./tests/ --profile black

check-sort-imports: ## Check import sorting
@echo "🔍 Checking import sorting"
uv run isort ./sql_redis ./tests/ --check-only --profile black

check-types: ## Run mypy type checking
@echo "🔍 Running mypy type checking"
uv run python -m mypy ./sql_redis

lint: format check-types ## Run all linting (format + type check)

test: ## Run tests (pass extra args with ARGS="...")
@echo "🧪 Running tests"
uv run python -m pytest $(ARGS)

test-verbose: ## Run tests with verbose output
@echo "🧪 Running tests (verbose)"
uv run python -m pytest -vv -s $(ARGS)

test-cov: ## Run tests with coverage report
@echo "🧪 Running tests with coverage"
uv run python -m pytest --cov=sql_redis --cov-report=term-missing --cov-report=html $(ARGS)

check: lint test ## Run all checks (lint + test)

build: ## Build wheel and source distribution
@echo "🏗️ Building distribution packages"
uv build

clean: ## Clean up build artifacts and caches
@echo "🧹 Cleaning up directory"
find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
find . -type d -name ".pytest_cache" -exec rm -rf {} + 2>/dev/null || true
find . -type d -name ".mypy_cache" -exec rm -rf {} + 2>/dev/null || true
find . -type d -name ".coverage" -delete 2>/dev/null || true
find . -type d -name "htmlcov" -exec rm -rf {} + 2>/dev/null || true
find . -type d -name "dist" -exec rm -rf {} + 2>/dev/null || true
find . -type d -name "build" -exec rm -rf {} + 2>/dev/null || true
find . -type d -name "*.egg-info" -exec rm -rf {} + 2>/dev/null || true
find . -type f -name "*.log" -exec rm -rf {} + 2>/dev/null || true

help: ## Show this help message
@echo "Available commands:"
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-20s\033[0m %s\n", $$1, $$2}'

79 changes: 61 additions & 18 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,77 @@
name = "sql-redis"
version = "0.1.0"
description = "SQL to Redis command translation utility"
requires-python = ">=3.11"
authors = [{ name = "Redis Inc.", email = "applied.ai@redis.com" }]
requires-python = ">=3.9,<3.14"
readme = "README.md"
license = "MIT"
keywords = [
"sql",
"redis",
"redis-client",
"query-translation",
]
classifiers = [
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"License :: OSI Approved :: MIT License",
]
dependencies = [
"redis>=5.0.0",
"sqlglot>=26.0.0",
]

[project.optional-dependencies]
dev = [
"pytest>=8.0.0",
"pytest-cov>=4.0.0",
"testcontainers[redis]>=4.0.0",
]
[project.urls]
Homepage = "https://github.com/redis/sql-redis"
Repository = "https://github.com/redis/sql-redis"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "--cov=sql_redis --cov-report=term-missing"
[dependency-groups]
dev = [
"black>=25.1.0,<26",
"isort>=5.6.4,<6",
"mypy>=1.11.0,<2",
"pytest>=8.0.0,<9",
"pytest-cov>=4.0.0,<5",
"pytest-asyncio>=0.23.6,<0.24",
"pre-commit>=4.1.0,<5",
"codespell>=2.4.1,<3",
"testcontainers[redis]>=4.0.0,<5",
]

[tool.coverage.run]
source = ["sql_redis"]
branch = true
[tool.uv]
default-groups = ["dev"]

[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"raise NotImplementedError",
]
[tool.black]
target-version = ['py39', 'py310', 'py311', 'py312', 'py313']
exclude = '''
(
| \.egg
| \.git
| \.hg
| \.mypy_cache
| \.nox
| \.tox
| \.venv
| _build
| build
| dist
| setup.py
)
'''

[tool.pytest.ini_options]
log_cli = true
asyncio_mode = "auto"

[tool.mypy]
warn_unused_configs = true
ignore_missing_imports = true
exclude = ["env", "venv", ".venv"]

3 changes: 1 addition & 2 deletions sql_redis/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""SQL to Redis command translation utility."""

from sql_redis.translator import Translator, TranslatedQuery
from sql_redis.translator import TranslatedQuery, Translator

__all__ = ["Translator", "TranslatedQuery"]

15 changes: 9 additions & 6 deletions sql_redis/analyzer.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
"""SQL analyzer component - resolves field types from schema."""

from __future__ import annotations

from dataclasses import dataclass, field

from sql_redis.parser import ParsedQuery, AggregationSpec, ComputedField, Condition
from sql_redis.parser import AggregationSpec, ComputedField, Condition, ParsedQuery


@dataclass
class VectorSearchAnalysis:
"""Analyzed vector search details."""

field: str
k: int
alias: str
Expand All @@ -16,7 +19,7 @@ class VectorSearchAnalysis:
@dataclass
class AnalyzedQuery:
"""Result of analyzing a parsed SQL query with schema context."""

parsed: ParsedQuery = field(default_factory=ParsedQuery)
field_types: dict[str, str] = field(default_factory=dict)
aggregations: list[AggregationSpec] = field(default_factory=list)
Expand All @@ -25,15 +28,16 @@ class AnalyzedQuery:
is_global_aggregation: bool = False
vector_search: VectorSearchAnalysis | None = None
has_prefilter: bool = False

def get_field_type(self, field_name: str) -> str | None:
"""Get the type of a field."""
return self.field_types.get(field_name)

def get_conditions_by_type(self, field_type: str) -> list[Condition]:
"""Get conditions for fields of a specific type."""
return [
c for c in self.parsed.conditions
c
for c in self.parsed.conditions
if self.field_types.get(c.field) == field_type
]

Expand Down Expand Up @@ -127,4 +131,3 @@ def analyze(self, parsed: ParsedQuery) -> AnalyzedQuery:
result.has_prefilter = len(parsed.conditions) > 0

return result

12 changes: 6 additions & 6 deletions sql_redis/executor.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""SQL Executor - executes translated queries against Redis."""

from __future__ import annotations

from dataclasses import dataclass

import redis
Expand All @@ -25,9 +27,7 @@ def __init__(self, client: redis.Redis, schema_registry: SchemaRegistry):
self._schema_registry = schema_registry
self._translator = Translator(schema_registry)

def execute(
self, sql: str, *, params: dict | None = None
) -> QueryResult:
def execute(self, sql: str, *, params: dict | None = None) -> QueryResult:
"""Execute a SQL query and return results."""
params = params or {}

Expand All @@ -44,10 +44,11 @@ def execute(
translated = self._translator.translate(sql)

# Build command list and substitute vector params
cmd = list(translated.to_command_list())
# Use list[str | bytes] to allow bytes for vector params
cmd: list[str | bytes] = list(translated.to_command_list())

# Find any bytes params (vectors) to substitute
vector_param = None
vector_param: bytes | None = None
for value in params.values():
if isinstance(value, bytes):
vector_param = value
Expand Down Expand Up @@ -80,4 +81,3 @@ def execute(
rows.append(row)

return QueryResult(rows=rows, count=count)

Loading
Loading