Name | qlsq JSON |
Version |
0.5.0
JSON |
| download |
home_page | None |
Summary | dynamic sql generetor |
upload_time | 2025-07-17 19:23:10 |
maintainer | None |
docs_url | None |
author | None |
requires_python | >=3.12 |
license | None |
keywords |
sql
postgresql
sql-generator
|
VCS |
|
bugtrack_url |
|
requirements |
No requirements were recorded.
|
Travis-CI |
No Travis.
|
coveralls test coverage |
No coveralls.
|
# qlsq (QL²) - Predictable SQL Query Generation
[](https://pypi.org/project/qlsq/)
**qlsq** (QL-squared) is a Python library for generating predictable, secure SQL queries from Lisp-like query expressions. It helps you build complex database queries while maintaining control over performance and security.
## ⨠Key Features
- **Prevents N+1 Query Problems** - Results nesing is not allowed
- **Query Complexity Control** - Restrict filtering to indexed columns only
- **Smart Join Management** - Only performs LEFT JOINs for actually selected fields
- **Claim-Based Access Control** - Fine-grained permissions for each field (read/edit/filter)
- **Predictable Output** - Generates clean, parameterized SQL queries
- **PostgreSQL Integration** - Works seamlessly with psycopg, generating parameterized queries
## š Installation
```bash
pip install qlsq
# or
uv add qlsq
# or
poetry add qlsq
```
## š Requirements
- Python 3.12+
- psycopg2 or psycopg3 (for PostgreSQL integration)
## šÆ Use Cases
Perfect for applications that need:
- Dynamic query building from frontend filters
- Multi-tenant applications with complex permissions
- APIs that expose flexible data querying capabilities
- Applications requiring predictable query performance
## š Quick Start
### 1. Define Your Context
Every query operates within a context that defines tables and fields:
```python
from qlsq import ContextTable, ContextField, Context, QueryType
# Define tables and their relationships
tables = [
ContextTable(
alias="ut",
source="user_tasks",
join_condition=None, # Root table
depends_on=[]
),
ContextTable(
alias="u",
source="users",
join_condition="u.id = ut.user_id",
depends_on=["ut"] # Depends on user_tasks table
),
]
# Define available fields with permissions
fields = [
ContextField(
alias="full_name",
source="full_name",
query_type=QueryType.text,
table_alias="u",
read_claim="r_full_name",
edit_claim="e_full_name",
filter_claim="f_full_name",
),
ContextField(
alias="user_id",
source="user_id",
query_type=QueryType.numeric,
table_alias="ut",
read_claim="r_user_id",
edit_claim="e_user_id",
filter_claim="f_user_id",
),
]
# Create the context
context = Context(tables, fields)
# Create the context registry
context_registry = ContextRegistry(
{
"user_tasks": context,
}
)
```
### 2. Write Lisp-Like Queries
```python
# Simple query: SELECT full_name WHERE user_id = 3
query_expression = [
["using", "user_tasks"], # specify context
["select", "full_name"],
["where", ["eq", "user_id", 3]]
]
```
### 3. Generate SQL (Two Approaches)
**Approach A: Parse then generate**
```python
# Parse and generate SQL
query = context.parse_query(query_expression)
sql, params = query.to_sql()
print("Generated SQL:")
print(sql)
# Output: SELECT u.full_name FROM user_tasks ut LEFT JOIN users u ON u.id = ut.user_id WHERE (ut.user_id = %(param_0)s);
print("Parameters:")
print(params)
# Output: {"param_0": 3}
```
**Approach B: Direct generation with claims**
```python
# Generate SQL directly with claims validation
user_claims = ["r_full_name", "f_user_id"] # User's permissions
sql, params = context.to_sql(query_expression, user_claims)
```
### 4. Claims-Based Security
```python
# Define user permissions
user_claims = ["r_full_name", "f_user_id"] # Can read full_name, filter by user_id
# This will work - user has required claims
query = context.parse_query([["select", "full_name"], ["where", ["eq", "user_id", 3]]])
query.assert_claims(user_claims) # Validates permissions
# This will fail - user lacks r_user_id claim
try:
query = context.parse_query([["select", "user_id"]])
query.assert_claims(user_claims) # Raises MissingClaimsError
except MissingClaimsError as e:
print(f"Access denied: {e}")
```
### 5. Execute with psycopg
```python
import psycopg2
# Execute the query
with psycopg2.connect(database_url) as conn:
with conn.cursor() as cursor:
cursor.execute(sql, params)
results = cursor.fetchall()
```
## š Advanced Examples
### Complex Filtering
```python
# Multiple conditions with AND/OR logic
query = [
["using", "user_tasks"],
["select", "full_name", "user_id"],
["where", [
"and",
["eq", "user_id", 3],
["like", "full_name", ["str", "%john%"]]
]]
]
```
### Mathematical Operations
```python
# Arithmetic operations
query = [
["select", ["add", "field1", "field2"]], # Addition
["select", ["sub", "field1", "field2"]], # Subtraction
["select", ["mul", "field1", "field2"]], # Multiplication
["select", ["div", "field1", "field2"]], # Division
]
```
### String Operations
```python
# String manipulation
query = [
["select", ["concat", "first_name", ["str", " "], "last_name"]], # Concatenation
["select", ["lower", "full_name"]], # Lowercase
["select", ["upper", "full_name"]], # Uppercase
]
```
### Date Handling
```python
# Date operations
query = [
["where", ["eq", "created_at", ["date", "2024-01-15T10:30:00Z"]]]
]
```
### Null Checks and Coalescing
```python
# Working with NULL values
query = [
["select", ["coalesce", "nickname", ["str", "-NA-"]]],
["where", ["is_not_null", "email"]], # Check for non-null
]
```
### Sorting and Limiting
```python
# Add sorting and pagination
query = [
["using", "users"],
["select", "full_name", "user_id"],
["where", ["gt", "user_id", 0]],
["orderby", ["asc", "full_name"]], # Note: asc/desc wraps the field
["limit", 10],
["offset", 20]
]
```
### IN Clause and Complex Conditions
```python
# Multiple values and complex logic
query = [
["using", "users"],
["select", "full_name"],
["where", [
"or",
["in", "user_id", 1, 2, 3, 4],
["and",
["gte", "age", 18],
["like", "email", ["str", "%@company.com"]]
]
]]
]
```
## š”ļø Security Features
### Claim-Based Access Control
```python
# Only users with proper claims can access fields
user_claims = ["r_full_name", "f_user_id"] # Can read full_name, filter by user_id
sql, params = context.to_sql(query_expression, user_claims) # Will raise MissingClaimsError if claims are missing
```
### Query Validation
- Prevents filtering on non-indexed columns (if configured)
- Validates field access permissions
- Ensures proper table relationships
- Protects against SQL injection through parameterization
## šØ Query Language Reference
### Core Operations
| Operation | Syntax | Example |
|-----------|--------|---------|
| Select | `["select", "field1", "field2"]` | `["select", "name", "email"]` |
| Where | `["where", condition]` | `["where", ["eq", "id", 1]]` |
### Comparison Operators
| Operation | Syntax | Example |
|-----------|--------|---------|
| Equals | `["eq", field, value]` | `["eq", "status", ["str", "active"]]` |
| Not Equals | `["neq", field, value]` | `["neq", "status", ["str", "deleted"]]` |
| Greater Than | `["gt", field, value]` | `["gt", "age", 18]` |
| Greater/Equal | `["gte", field, value]` | `["gte", "score", 100]` |
| Less Than | `["lt", field, value]` | `["lt", "price", 50]` |
| Less/Equal | `["lte", field, value]` | `["lte", "quantity", 10]` |
| Like Pattern | `["like", field, pattern]` | `["like", "name", ["str", "%john%"]]` |
| In List | `["in", field, val1, val2, ...]` | `["in", "id", 1, 2, 3]` |
### Logical Operators
| Operation | Syntax | Example |
|-----------|--------|---------|
| And | `["and", cond1, cond2, ...]` | `["and", ["eq", "a", 1], ["eq", "b", 2]]` |
| Or | `["or", cond1, cond2, ...]` | `["or", ["eq", "status", "active"], ["eq", "status", "pending"]]` |
| Not | `["not", condition]` | `["not", ["eq", "deleted", true]]` |
### Null Checks
| Operation | Syntax | Example |
|-----------|--------|---------|
| Is Null | `["is_null", field]` | `["is_null", "deleted_at"]` |
| Is Not Null | `["is_not_null", field]` | `["is_not_null", "email"]` |
### Mathematical Operations
| Operation | Syntax | Example |
|-----------|--------|---------|
| Addition | `["add", expr1, expr2, ...]` | `["add", "base_price", "tax"]` |
| Subtraction | `["sub", expr1, expr2, ...]` | `["sub", "total", "discount"]` |
| Multiplication | `["mul", expr1, expr2, ...]` | `["mul", "price", "quantity"]` |
| Division | `["div", expr1, expr2]` | `["div", "total", "count"]` |
### String Operations
| Operation | Syntax | Example |
|-----------|--------|---------|
| Concatenate | `["concat", str1, str2, ...]` | `["concat", "first_name", ["str", " "], "last_name"]` |
| Lowercase | `["lower", string_expr]` | `["lower", "email"]` |
| Uppercase | `["upper", string_expr]` | `["upper", "code"]` |
| Coalesce | `["coalesce", expr1, expr2, ...]` | `["coalesce", "nickname", ["str", "-NA-"]]` |
### Literal Values
| Type | Syntax | Example |
|------|--------|---------|
| String | `["str", "value"]` | `["str", "hello world"]` |
| Date | `["date", "iso_string"]` | `["date", "2024-01-15T10:30:00Z"]` |
| Integer | `42` | `["eq", "age", 25]` |
| Float | `3.14` | `["eq", "price", 19.99]` |
| Boolean | `true`/`false` | `["eq", "active", true]` |
| Null | `null` | `["eq", "deleted_at", null]` |
### Ordering and Pagination
| Operation | Syntax | Example |
|-----------|--------|---------|
| Order By | `["orderby", direction1, direction2, ...]` | `["orderby", ["asc", "name"], ["desc", "created_at"]]` |
| Ascending | `["asc", field]` | `["asc", "name"]` |
| Descending | `["desc", field]` | `["desc", "created_at"]` |
| Limit | `["limit", count]` | `["limit", 10]` |
| Offset | `["offset", count]` | `["offset", 20]` |
## ā ļø Limitations
- **No nested queries** - Complex nesting must be implemented in SQL
- **PostgreSQL only** - Currently only supports PostgreSQL via psycopg
- **Left joins only** - Developer must ensure there are no unwanted duplicates
## š§ API Reference
`FIXME`: list main classes, methods, exceptions
## š¤ Contributing
Contributions are welcome! `main` branch is for development, each release and subseaquent hotfixes land on separate branches like `v0.1.3`.
## š License
This project is licensed under the MIT License - see the LICENSE file for details.
## š Links
- [PyPI Package](https://pypi.org/project/qlsq/)
Raw data
{
"_id": null,
"home_page": null,
"name": "qlsq",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.12",
"maintainer_email": null,
"keywords": "sql, postgresql, sql-generator",
"author": null,
"author_email": "Marius Kavaliauskas <mariuskava+qlsq@gmail.com>",
"download_url": "https://files.pythonhosted.org/packages/02/80/6f863253f7d00798ccbebf1a117efe8c8ea7afad9891217204ef2e58adfe/qlsq-0.5.0.tar.gz",
"platform": null,
"description": "# qlsq (QL\u00b2) - Predictable SQL Query Generation\n\n[](https://pypi.org/project/qlsq/)\n\n**qlsq** (QL-squared) is a Python library for generating predictable, secure SQL queries from Lisp-like query expressions. It helps you build complex database queries while maintaining control over performance and security.\n\n## \u2728 Key Features\n\n- **Prevents N+1 Query Problems** - Results nesing is not allowed\n- **Query Complexity Control** - Restrict filtering to indexed columns only\n- **Smart Join Management** - Only performs LEFT JOINs for actually selected fields\n- **Claim-Based Access Control** - Fine-grained permissions for each field (read/edit/filter)\n- **Predictable Output** - Generates clean, parameterized SQL queries\n- **PostgreSQL Integration** - Works seamlessly with psycopg, generating parameterized queries\n\n## \ud83d\ude80 Installation\n\n```bash\npip install qlsq\n# or\nuv add qlsq\n# or\npoetry add qlsq\n```\n\n## \ud83d\udccb Requirements\n\n- Python 3.12+\n- psycopg2 or psycopg3 (for PostgreSQL integration)\n\n## \ud83c\udfaf Use Cases\n\nPerfect for applications that need:\n- Dynamic query building from frontend filters\n- Multi-tenant applications with complex permissions\n- APIs that expose flexible data querying capabilities\n- Applications requiring predictable query performance\n\n## \ud83d\udcd6 Quick Start\n\n### 1. Define Your Context\n\nEvery query operates within a context that defines tables and fields:\n\n```python\nfrom qlsq import ContextTable, ContextField, Context, QueryType\n\n# Define tables and their relationships\ntables = [\n ContextTable(\n alias=\"ut\",\n source=\"user_tasks\", \n join_condition=None, # Root table\n depends_on=[]\n ),\n ContextTable(\n alias=\"u\",\n source=\"users\",\n join_condition=\"u.id = ut.user_id\",\n depends_on=[\"ut\"] # Depends on user_tasks table\n ),\n]\n\n# Define available fields with permissions\nfields = [\n ContextField(\n alias=\"full_name\",\n source=\"full_name\",\n query_type=QueryType.text,\n table_alias=\"u\",\n read_claim=\"r_full_name\",\n edit_claim=\"e_full_name\", \n filter_claim=\"f_full_name\",\n ),\n ContextField(\n alias=\"user_id\",\n source=\"user_id\",\n query_type=QueryType.numeric,\n table_alias=\"ut\",\n read_claim=\"r_user_id\",\n edit_claim=\"e_user_id\",\n filter_claim=\"f_user_id\",\n ),\n]\n\n# Create the context\ncontext = Context(tables, fields)\n\n# Create the context registry\ncontext_registry = ContextRegistry(\n {\n \"user_tasks\": context,\n }\n)\n```\n\n### 2. Write Lisp-Like Queries\n\n```python\n# Simple query: SELECT full_name WHERE user_id = 3\nquery_expression = [\n [\"using\", \"user_tasks\"], # specify context\n [\"select\", \"full_name\"],\n [\"where\", [\"eq\", \"user_id\", 3]]\n]\n```\n\n### 3. Generate SQL (Two Approaches)\n\n**Approach A: Parse then generate**\n```python\n# Parse and generate SQL\nquery = context.parse_query(query_expression)\nsql, params = query.to_sql()\n\nprint(\"Generated SQL:\")\nprint(sql)\n# Output: SELECT u.full_name FROM user_tasks ut LEFT JOIN users u ON u.id = ut.user_id WHERE (ut.user_id = %(param_0)s);\n\nprint(\"Parameters:\")\nprint(params)\n# Output: {\"param_0\": 3}\n```\n\n**Approach B: Direct generation with claims**\n```python\n# Generate SQL directly with claims validation\nuser_claims = [\"r_full_name\", \"f_user_id\"] # User's permissions\nsql, params = context.to_sql(query_expression, user_claims)\n```\n\n### 4. Claims-Based Security\n\n```python\n# Define user permissions\nuser_claims = [\"r_full_name\", \"f_user_id\"] # Can read full_name, filter by user_id\n\n# This will work - user has required claims\nquery = context.parse_query([[\"select\", \"full_name\"], [\"where\", [\"eq\", \"user_id\", 3]]])\nquery.assert_claims(user_claims) # Validates permissions\n\n# This will fail - user lacks r_user_id claim\ntry:\n query = context.parse_query([[\"select\", \"user_id\"]])\n query.assert_claims(user_claims) # Raises MissingClaimsError\nexcept MissingClaimsError as e:\n print(f\"Access denied: {e}\")\n```\n\n### 5. Execute with psycopg\n\n```python\nimport psycopg2\n\n# Execute the query\nwith psycopg2.connect(database_url) as conn:\n with conn.cursor() as cursor:\n cursor.execute(sql, params)\n results = cursor.fetchall()\n```\n\n## \ud83d\udd0d Advanced Examples\n\n### Complex Filtering\n\n```python\n# Multiple conditions with AND/OR logic\nquery = [\n [\"using\", \"user_tasks\"],\n [\"select\", \"full_name\", \"user_id\"],\n [\"where\", [\n \"and\",\n [\"eq\", \"user_id\", 3],\n [\"like\", \"full_name\", [\"str\", \"%john%\"]]\n ]]\n]\n```\n\n### Mathematical Operations\n\n```python\n# Arithmetic operations\nquery = [\n [\"select\", [\"add\", \"field1\", \"field2\"]], # Addition\n [\"select\", [\"sub\", \"field1\", \"field2\"]], # Subtraction \n [\"select\", [\"mul\", \"field1\", \"field2\"]], # Multiplication\n [\"select\", [\"div\", \"field1\", \"field2\"]], # Division\n]\n```\n\n### String Operations\n\n```python\n# String manipulation\nquery = [\n [\"select\", [\"concat\", \"first_name\", [\"str\", \" \"], \"last_name\"]], # Concatenation\n [\"select\", [\"lower\", \"full_name\"]], # Lowercase\n [\"select\", [\"upper\", \"full_name\"]], # Uppercase\n]\n```\n\n### Date Handling\n\n```python\n# Date operations\nquery = [\n [\"where\", [\"eq\", \"created_at\", [\"date\", \"2024-01-15T10:30:00Z\"]]]\n]\n```\n\n### Null Checks and Coalescing\n\n```python\n# Working with NULL values\nquery = [\n [\"select\", [\"coalesce\", \"nickname\", [\"str\", \"-NA-\"]]],\n [\"where\", [\"is_not_null\", \"email\"]], # Check for non-null\n]\n```\n\n### Sorting and Limiting\n\n```python\n# Add sorting and pagination\nquery = [\n [\"using\", \"users\"],\n [\"select\", \"full_name\", \"user_id\"],\n [\"where\", [\"gt\", \"user_id\", 0]],\n [\"orderby\", [\"asc\", \"full_name\"]], # Note: asc/desc wraps the field\n [\"limit\", 10],\n [\"offset\", 20]\n]\n```\n\n### IN Clause and Complex Conditions\n\n```python\n# Multiple values and complex logic\nquery = [\n [\"using\", \"users\"],\n [\"select\", \"full_name\"],\n [\"where\", [\n \"or\",\n [\"in\", \"user_id\", 1, 2, 3, 4],\n [\"and\", \n [\"gte\", \"age\", 18],\n [\"like\", \"email\", [\"str\", \"%@company.com\"]]\n ]\n ]]\n]\n```\n\n## \ud83d\udee1\ufe0f Security Features\n\n### Claim-Based Access Control\n\n```python\n# Only users with proper claims can access fields\nuser_claims = [\"r_full_name\", \"f_user_id\"] # Can read full_name, filter by user_id\nsql, params = context.to_sql(query_expression, user_claims) # Will raise MissingClaimsError if claims are missing\n```\n\n### Query Validation\n\n- Prevents filtering on non-indexed columns (if configured)\n- Validates field access permissions\n- Ensures proper table relationships\n- Protects against SQL injection through parameterization\n\n## \ud83c\udfa8 Query Language Reference\n\n### Core Operations\n| Operation | Syntax | Example |\n|-----------|--------|---------|\n| Select | `[\"select\", \"field1\", \"field2\"]` | `[\"select\", \"name\", \"email\"]` |\n| Where | `[\"where\", condition]` | `[\"where\", [\"eq\", \"id\", 1]]` |\n\n### Comparison Operators\n| Operation | Syntax | Example |\n|-----------|--------|---------|\n| Equals | `[\"eq\", field, value]` | `[\"eq\", \"status\", [\"str\", \"active\"]]` |\n| Not Equals | `[\"neq\", field, value]` | `[\"neq\", \"status\", [\"str\", \"deleted\"]]` |\n| Greater Than | `[\"gt\", field, value]` | `[\"gt\", \"age\", 18]` |\n| Greater/Equal | `[\"gte\", field, value]` | `[\"gte\", \"score\", 100]` |\n| Less Than | `[\"lt\", field, value]` | `[\"lt\", \"price\", 50]` |\n| Less/Equal | `[\"lte\", field, value]` | `[\"lte\", \"quantity\", 10]` |\n| Like Pattern | `[\"like\", field, pattern]` | `[\"like\", \"name\", [\"str\", \"%john%\"]]` |\n| In List | `[\"in\", field, val1, val2, ...]` | `[\"in\", \"id\", 1, 2, 3]` |\n\n### Logical Operators\n| Operation | Syntax | Example |\n|-----------|--------|---------|\n| And | `[\"and\", cond1, cond2, ...]` | `[\"and\", [\"eq\", \"a\", 1], [\"eq\", \"b\", 2]]` |\n| Or | `[\"or\", cond1, cond2, ...]` | `[\"or\", [\"eq\", \"status\", \"active\"], [\"eq\", \"status\", \"pending\"]]` |\n| Not | `[\"not\", condition]` | `[\"not\", [\"eq\", \"deleted\", true]]` |\n\n### Null Checks\n| Operation | Syntax | Example |\n|-----------|--------|---------|\n| Is Null | `[\"is_null\", field]` | `[\"is_null\", \"deleted_at\"]` |\n| Is Not Null | `[\"is_not_null\", field]` | `[\"is_not_null\", \"email\"]` |\n\n### Mathematical Operations\n| Operation | Syntax | Example |\n|-----------|--------|---------|\n| Addition | `[\"add\", expr1, expr2, ...]` | `[\"add\", \"base_price\", \"tax\"]` |\n| Subtraction | `[\"sub\", expr1, expr2, ...]` | `[\"sub\", \"total\", \"discount\"]` |\n| Multiplication | `[\"mul\", expr1, expr2, ...]` | `[\"mul\", \"price\", \"quantity\"]` |\n| Division | `[\"div\", expr1, expr2]` | `[\"div\", \"total\", \"count\"]` |\n\n### String Operations\n| Operation | Syntax | Example |\n|-----------|--------|---------|\n| Concatenate | `[\"concat\", str1, str2, ...]` | `[\"concat\", \"first_name\", [\"str\", \" \"], \"last_name\"]` |\n| Lowercase | `[\"lower\", string_expr]` | `[\"lower\", \"email\"]` |\n| Uppercase | `[\"upper\", string_expr]` | `[\"upper\", \"code\"]` |\n| Coalesce | `[\"coalesce\", expr1, expr2, ...]` | `[\"coalesce\", \"nickname\", [\"str\", \"-NA-\"]]` |\n\n### Literal Values\n| Type | Syntax | Example |\n|------|--------|---------|\n| String | `[\"str\", \"value\"]` | `[\"str\", \"hello world\"]` |\n| Date | `[\"date\", \"iso_string\"]` | `[\"date\", \"2024-01-15T10:30:00Z\"]` |\n| Integer | `42` | `[\"eq\", \"age\", 25]` |\n| Float | `3.14` | `[\"eq\", \"price\", 19.99]` |\n| Boolean | `true`/`false` | `[\"eq\", \"active\", true]` |\n| Null | `null` | `[\"eq\", \"deleted_at\", null]` |\n\n### Ordering and Pagination\n| Operation | Syntax | Example |\n|-----------|--------|---------|\n| Order By | `[\"orderby\", direction1, direction2, ...]` | `[\"orderby\", [\"asc\", \"name\"], [\"desc\", \"created_at\"]]` |\n| Ascending | `[\"asc\", field]` | `[\"asc\", \"name\"]` |\n| Descending | `[\"desc\", field]` | `[\"desc\", \"created_at\"]` |\n| Limit | `[\"limit\", count]` | `[\"limit\", 10]` |\n| Offset | `[\"offset\", count]` | `[\"offset\", 20]` |\n\n## \u26a0\ufe0f Limitations\n\n- **No nested queries** - Complex nesting must be implemented in SQL\n- **PostgreSQL only** - Currently only supports PostgreSQL via psycopg\n- **Left joins only** - Developer must ensure there are no unwanted duplicates\n\n## \ud83d\udd27 API Reference\n\n`FIXME`: list main classes, methods, exceptions\n\n\n## \ud83e\udd1d Contributing\n\nContributions are welcome! `main` branch is for development, each release and subseaquent hotfixes land on separate branches like `v0.1.3`.\n\n## \ud83d\udcc4 License\n\nThis project is licensed under the MIT License - see the LICENSE file for details.\n\n## \ud83d\udd17 Links\n\n- [PyPI Package](https://pypi.org/project/qlsq/)\n",
"bugtrack_url": null,
"license": null,
"summary": "dynamic sql generetor",
"version": "0.5.0",
"project_urls": {
"Homepage": "https://gitlab.com/qlsq/qlsq",
"Issue Tracker": "https://gitlab.com/qlsq/qlsq/-/issues",
"Source Code": "https://gitlab.com/qlsq/qlsq"
},
"split_keywords": [
"sql",
" postgresql",
" sql-generator"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "137dd04952370491ecaf5b3fdaf82e9fe8dc30d496393542ccf407770dc4b6b9",
"md5": "4eae31c9573565270e1e01db2d716ec3",
"sha256": "defa19c14d4c06ad1ee4d965a2efe8be39fb0462872f92b29a8bf70014a7116f"
},
"downloads": -1,
"filename": "qlsq-0.5.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "4eae31c9573565270e1e01db2d716ec3",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.12",
"size": 11022,
"upload_time": "2025-07-17T19:23:09",
"upload_time_iso_8601": "2025-07-17T19:23:09.466072Z",
"url": "https://files.pythonhosted.org/packages/13/7d/d04952370491ecaf5b3fdaf82e9fe8dc30d496393542ccf407770dc4b6b9/qlsq-0.5.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "02806f863253f7d00798ccbebf1a117efe8c8ea7afad9891217204ef2e58adfe",
"md5": "7526a6c3f8c685f8b233c7d28e16fd50",
"sha256": "064e43470fbda9465ee1570dd8ebcf96408387c02bc961b60214334afed45a3f"
},
"downloads": -1,
"filename": "qlsq-0.5.0.tar.gz",
"has_sig": false,
"md5_digest": "7526a6c3f8c685f8b233c7d28e16fd50",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.12",
"size": 14822,
"upload_time": "2025-07-17T19:23:10",
"upload_time_iso_8601": "2025-07-17T19:23:10.981132Z",
"url": "https://files.pythonhosted.org/packages/02/80/6f863253f7d00798ccbebf1a117efe8c8ea7afad9891217204ef2e58adfe/qlsq-0.5.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-07-17 19:23:10",
"github": false,
"gitlab": true,
"bitbucket": false,
"codeberg": false,
"gitlab_user": "qlsq",
"gitlab_project": "qlsq",
"lcname": "qlsq"
}