Q-strings: query anything
Project description
Q Strings
Once upon a time, there was a three-line class for running SQL queries as a method on a string.
class Q(str):
def run(self):
return duckdb.sql(self)
# usage
Q(my_query).run()
Installation
pip install qstrings # base only
pip install qstrings[all] # with ai and cli
Why?
What if the string itself was the * of the show? Q-string is simply a string extra methods and attributes that unlock a few useful tricks.
1. Composable queries and engines
SQL-oriented python libraries usually pass a string to a SQL engine. If you routinely work with multiple databases, the variations in dialects and in implementation can pile up into something awkward.
Q-strings help in two ways. First, the queries are parsed using sqlglot:
q = Q("SELECT 42 LIMIT 1")
q.ast # abstract syntax tree
The q.ast carries a rich toolset, including transpiling to different dialects and swapping clauses are perhaps most useful:
q1 = Q("SELECT 42 FROM one_table")
q2 = q1.ast.from_("another_table").q()
assert q2 == "SELECT 42 FROM another_table"
assert q.transpile(read="duckdb", write="tsql") == Q("SELECT TOP 1 42")
The second helpful pattern is the template pattern for easily defining engines. The default query engine is DuckDB, q.run() and q.run(engine="duckdb") are equivalent and will execute a DuckDB query. It is obviously impossible to cover all possible scenarios; hard-coding the engine selection logic if engine == "this": run_that() only gets you so far. To make another engine, subclass Engine and write the run method. The new engine becomes available right away, at runtime, thanks to autoregistry.
from qstrings import Engine, Q
class FunnyDuckDBEngine(Engine):
"""The engine name will be "FunnyDuckDB" (dropping 'Engine', not case sensitive)."""
def run(q: Q):
result = duckdb.sql(q)
funny = "lol, running a funny query"
return result,
Q("SELECT 42 AS haha").run(engine="FunnyDuckDB")
A more serious example might be this implementation for Athena. It uses awswrangler, handles different profiles, and sets certain options:
class AthenaEngine(Engine):
def df(self, **kwargs) -> pd.DataFrame:
import awswrangler as wr
import boto3
if not boto3.DEFAULT_SESSION or kwargs.get("profile"):
boto3.setup_default_session(
region_name=kwargs.get("region", "us-east-1"),
profile_name=kwargs.get("profile")
)
df = wr.athena.read_sql_query(
self,
database=kwargs.get("database", "default"),
ctas_approach=kwargs.get("ctas", False),
keep_files=kwargs.get("keep_files", False),
)
return df
Q("SHOW CREATE TABLE db.table").run(engine="athena")
2. Format strings with variables
from qstrings import Q
# easily swap tables, filters, column lists
Q("SELECT * FROM {table}", table="prod.whatever").run()
# or use f-strings
value = 42
Q(f"SELECT * FROM prod.whatever WHERE col = {value}").run()
# or both
cols = ",".join(["col1","col2"])
Q(f"SELECT col0,{cols} FROM prod.whatever WHERE col = {value}", value=1).run()
# use SQL templates
Q(file="tests/test_format.sql", num=42)
# variables can also come from env vars (keyword args take precedence)
Q("""
CREATE SECRET my_secret (
TYPE s3,
KEY_ID '{KEY_ID}'
SECRET '{SECRET_KEY}'
)
""").run()
# easy to write unit tests
def test_keys_given_in_env():"
s = "SELECT {num} AS {Q_name}"
os.environ["Q_name"] = "answer"
q = Q(s, num=42)
assert q == "SELECT 42 AS answer"
assert q.refs == {"num": 42, "Q_name": "answer"}
assert q.file is None
CLI
Installing qstrings[all] or qstrings[cli] gives access to executable q, powered by the excellent CLI library cyclopts:
q --help
Usage: run-query [ARGS] [OPTIONS]
Query anything!
╭─ Commands ─────────────────────────────────────────────────╮
│ --help -h Display this message and exit. │
│ --version Display application version. │
╰────────────────────────────────────────────────────────────╯
╭─ Parameters ───────────────────────────────────────────────╮
│ QUERY --query Query string │
│ --file -f File template │
│ --engine -e Query engine [default: duckdb] │
│ --model -m HuggingFace model [default: │
│ openai/gpt-oss-20b:fireworks-ai] │
│ -o Output format (defaults to │
│ whatever Engine.run() returns) │
│ [choices: engine, csv, list, line] │
│ [default: engine] │
│ --LIMIT -L Limit rows │
│ --COUNT --no-COUNT -C Return row count only [default: │
│ False] │
│ --[KEYWORD] │
╰────────────────────────────────────────────────────────────╯
The crown of creation: pipe results of an LLM query to a SQL query. The prompt provides an affinity data model and asks to find elements with 8 or more isotopes.
export URL="https://raw.githubusercontent.com/liquidcarbon/chembiodata/main/isotopes.csv"
q -f tests/test_prompt1.md -e hf | q -o csv
/* 250825@22:48:46.297|DEBUG|qstrings[0.2.1].Q.run:190|input_tokens=209 */
/* 250825@22:48:46.297|DEBUG|qstrings[0.2.1].Q.run:191|output_tokens=611 */
/* 250825@22:48:46.864|INFO|qstrings[0.2.1].Q.run:151|4 rows x 2 cols in 0.0000 sec */
Symbol,num_isotopes
Sn,10
Xe,9
Te,8
Cd,8
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file qstrings-0.2.2.tar.gz.
File metadata
- Download URL: qstrings-0.2.2.tar.gz
- Upload date:
- Size: 7.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
df83a83eea69a5c558e14ea40db39bd04d44056377e8ab08d915377eb38aefda
|
|
| MD5 |
22a177a6cac37dd542c766e01a785c75
|
|
| BLAKE2b-256 |
078a7ce38d2173aafd32be2750c2f802503b895c1d58b17f517f8c466485cc81
|
Provenance
The following attestation bundles were made for qstrings-0.2.2.tar.gz:
Publisher:
pypi-release.yml on liquidcarbon/qstrings
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
qstrings-0.2.2.tar.gz -
Subject digest:
df83a83eea69a5c558e14ea40db39bd04d44056377e8ab08d915377eb38aefda - Sigstore transparency entry: 434976438
- Sigstore integration time:
-
Permalink:
liquidcarbon/qstrings@e422b3ab74d34090f562039ccdc7a8684d7534ed -
Branch / Tag:
refs/heads/main - Owner: https://github.com/liquidcarbon
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi-release.yml@e422b3ab74d34090f562039ccdc7a8684d7534ed -
Trigger Event:
push
-
Statement type:
File details
Details for the file qstrings-0.2.2-py3-none-any.whl.
File metadata
- Download URL: qstrings-0.2.2-py3-none-any.whl
- Upload date:
- Size: 7.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a5c8197b05bed4a08a3e87e774304204ff890480e128d1b3f430327f57f882a8
|
|
| MD5 |
5b7a12c3b18567e6aa615fb2e0900e6c
|
|
| BLAKE2b-256 |
3927cdb3d3925fe131e787267da28cdcae53f958131a1bd1c3f21e9dd8144e4f
|
Provenance
The following attestation bundles were made for qstrings-0.2.2-py3-none-any.whl:
Publisher:
pypi-release.yml on liquidcarbon/qstrings
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
qstrings-0.2.2-py3-none-any.whl -
Subject digest:
a5c8197b05bed4a08a3e87e774304204ff890480e128d1b3f430327f57f882a8 - Sigstore transparency entry: 434976469
- Sigstore integration time:
-
Permalink:
liquidcarbon/qstrings@e422b3ab74d34090f562039ccdc7a8684d7534ed -
Branch / Tag:
refs/heads/main - Owner: https://github.com/liquidcarbon
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi-release.yml@e422b3ab74d34090f562039ccdc7a8684d7534ed -
Trigger Event:
push
-
Statement type: