t-sql 4.12.0__tar.gz → 4.13.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. t_sql-4.12.0/README.md → t_sql-4.13.0/PKG-INFO +71 -0
  2. t_sql-4.12.0/PKG-INFO → t_sql-4.13.0/README.md +60 -10
  3. {t_sql-4.12.0 → t_sql-4.13.0}/pyproject.toml +6 -4
  4. {t_sql-4.12.0 → t_sql-4.13.0}/.dockerignore +0 -0
  5. {t_sql-4.12.0 → t_sql-4.13.0}/.github/workflows/publish.yml +0 -0
  6. {t_sql-4.12.0 → t_sql-4.13.0}/.github/workflows/test.yml +0 -0
  7. {t_sql-4.12.0 → t_sql-4.13.0}/.gitignore +0 -0
  8. {t_sql-4.12.0 → t_sql-4.13.0}/Dockerfile +0 -0
  9. {t_sql-4.12.0 → t_sql-4.13.0}/LICENSE +0 -0
  10. {t_sql-4.12.0 → t_sql-4.13.0}/compose.yaml +0 -0
  11. {t_sql-4.12.0 → t_sql-4.13.0}/context7.json +0 -0
  12. {t_sql-4.12.0 → t_sql-4.13.0}/pytest.ini +0 -0
  13. {t_sql-4.12.0 → t_sql-4.13.0}/tests/test_alembic_integration.py +0 -0
  14. {t_sql-4.12.0 → t_sql-4.13.0}/tests/test_asyncpg_integration.py +0 -0
  15. {t_sql-4.12.0 → t_sql-4.13.0}/tests/test_deep_nesting.py +0 -0
  16. {t_sql-4.12.0 → t_sql-4.13.0}/tests/test_different_object_types.py +0 -0
  17. {t_sql-4.12.0 → t_sql-4.13.0}/tests/test_error_messages.py +0 -0
  18. {t_sql-4.12.0 → t_sql-4.13.0}/tests/test_escaped.py +0 -0
  19. {t_sql-4.12.0 → t_sql-4.13.0}/tests/test_escaped_binary_hex.py +0 -0
  20. {t_sql-4.12.0 → t_sql-4.13.0}/tests/test_helper_functions.py +0 -0
  21. {t_sql-4.12.0 → t_sql-4.13.0}/tests/test_injection_edge_cases.py +0 -0
  22. {t_sql-4.12.0 → t_sql-4.13.0}/tests/test_injection_protection_validation.py +0 -0
  23. {t_sql-4.12.0 → t_sql-4.13.0}/tests/test_injections_for_escaped.py +0 -0
  24. {t_sql-4.12.0 → t_sql-4.13.0}/tests/test_like_patterns.py +0 -0
  25. {t_sql-4.12.0 → t_sql-4.13.0}/tests/test_mysql_integration.py +0 -0
  26. {t_sql-4.12.0 → t_sql-4.13.0}/tests/test_parameter_names.py +0 -0
  27. {t_sql-4.12.0 → t_sql-4.13.0}/tests/test_query_builder.py +0 -0
  28. {t_sql-4.12.0 → t_sql-4.13.0}/tests/test_sqlalchemy_integration.py +0 -0
  29. {t_sql-4.12.0 → t_sql-4.13.0}/tests/test_sqlite_integration.py +0 -0
  30. {t_sql-4.12.0 → t_sql-4.13.0}/tests/test_string_based_builders.py +0 -0
  31. {t_sql-4.12.0 → t_sql-4.13.0}/tests/test_styles.py +0 -0
  32. {t_sql-4.12.0 → t_sql-4.13.0}/tests/test_template_in_builders.py +0 -0
  33. {t_sql-4.12.0 → t_sql-4.13.0}/tests/test_tsql.py +0 -0
  34. {t_sql-4.12.0 → t_sql-4.13.0}/tests/test_type_processor.py +0 -0
  35. {t_sql-4.12.0 → t_sql-4.13.0}/tsql/__init__.py +0 -0
  36. {t_sql-4.12.0 → t_sql-4.13.0}/tsql/query_builder.py +0 -0
  37. {t_sql-4.12.0 → t_sql-4.13.0}/tsql/row.py +0 -0
  38. {t_sql-4.12.0 → t_sql-4.13.0}/tsql/styles.py +0 -0
  39. {t_sql-4.12.0 → t_sql-4.13.0}/tsql/type_processor.py +0 -0
@@ -1,3 +1,14 @@
1
+ Metadata-Version: 2.4
2
+ Name: t-sql
3
+ Version: 4.13.0
4
+ Summary: Safe SQL. SQL queries for python t-strings (PEP 750)
5
+ Project-URL: Homepage, https://github.com/nhumrich/t-sql
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.14
8
+ Provides-Extra: sqlalchemy
9
+ Requires-Dist: sqlalchemy>=2.0.0; extra == 'sqlalchemy'
10
+ Description-Content-Type: text/markdown
11
+
1
12
  # t-sql
2
13
 
3
14
  A lightweight SQL templating library that leverages Python 3.14's t-strings (PEP 750).
@@ -300,6 +311,31 @@ query = (Posts.select()
300
311
  .left_join(Users, on=Posts.user_id == Users.id))
301
312
  ```
302
313
 
314
+ ### Raw JOIN clauses (escape hatch)
315
+
316
+ For join shapes the typed `join()`/`left_join()`/`right_join()` can't express —
317
+ LATERAL subqueries, ON-less cross joins, set-returning functions — use
318
+ `join_raw()` to splice a complete JOIN clause Template verbatim. The Template
319
+ must carry the **whole** clause including the join keyword; nothing is added
320
+ around it. Parameters inside the Template are still parameterized and renumber
321
+ correctly alongside the rest of the query.
322
+
323
+ ```python
324
+ query = (SelectQueryBuilder.from_table('records', schema='dataset', alias='t')
325
+ .select(t't.*')
326
+ .join_raw(t'CROSS JOIN LATERAL unnest(t.tags) AS tag'))
327
+
328
+ # Parameterized raw join, composed with a WHERE clause
329
+ query = (SelectQueryBuilder.from_table('records', alias='t')
330
+ .select(t't.*')
331
+ .join_raw(t'LEFT JOIN other o ON o.id = t.ref AND o.kind = {"x"}')
332
+ .where(t't.active = {True}'))
333
+ ```
334
+
335
+ > ⚠️ **Not advised.** `join_raw()` bypasses the builder's join structure. Prefer
336
+ > the typed `join()`/`left_join()`/`right_join()` with a Table + Condition
337
+ > wherever they suffice.
338
+
303
339
  ## Query Features
304
340
 
305
341
  ### Selecting All Columns from a Table
@@ -323,6 +359,34 @@ query = Posts.select(Posts.ALL, Users.ALL).join(Users, Posts.user_id == Users.id
323
359
 
324
360
  This is particularly useful when joining tables where you want all columns from one table but only specific columns from others.
325
361
 
362
+ ### DISTINCT ON (PostgreSQL)
363
+
364
+ `distinct_on()` emits a `SELECT DISTINCT ON (...)` clause. It accepts Column
365
+ objects, string column names, or raw Templates — coerced exactly like
366
+ `select()`.
367
+
368
+ ```python
369
+ query = Users.select(Users.id, Users.username).distinct_on('username')
370
+ # ('SELECT DISTINCT ON (username) users.id, users.username FROM users', [])
371
+
372
+ # Multiple columns are comma-joined inside the parens
373
+ query = Users.select(Users.id).distinct_on('username', 'email')
374
+ # ('SELECT DISTINCT ON (username, email) users.id FROM users', [])
375
+ ```
376
+
377
+ ### FROM ONLY and Table Aliases
378
+
379
+ When building from a string table name, `from_table()` accepts `only=True` to
380
+ emit `FROM ONLY {table}` (excludes inheriting child tables in PostgreSQL) and
381
+ `alias=...` to emit `FROM {table} AS {alias}` (the alias is identifier-validated):
382
+
383
+ ```python
384
+ query = (SelectQueryBuilder.from_table('records', schema='dataset', alias='t', only=True)
385
+ .select(t't.*')
386
+ .where(t't.active = {True}'))
387
+ # ('SELECT t.* FROM ONLY dataset.records AS t WHERE (t.active = $1)', [True])
388
+ ```
389
+
326
390
  ### NULL Checks and Other Operators
327
391
 
328
392
  ```python
@@ -349,6 +413,11 @@ query = Posts.select().order_by(Posts.id) # defaults to ASC
349
413
  query = Posts.select().order_by(Posts.id.desc())
350
414
  query = Posts.select().order_by(Posts.created_at.asc(), Posts.id.desc())
351
415
 
416
+ # ORDER BY / GROUP BY also accept raw Templates, emitted verbatim
417
+ # (parity with where()/having()/select()), for computed expressions:
418
+ query = Posts.select().order_by(t'lower(title) DESC')
419
+ query = Posts.select().group_by(t"date_trunc('day', created_at)")
420
+
352
421
  # LIMIT and OFFSET
353
422
  query = Posts.select().limit(10).offset(20)
354
423
 
@@ -579,6 +648,8 @@ pip install t-sql[sqlalchemy]
579
648
  uv add t-sql --optional sqlalchemy
580
649
  ```
581
650
 
651
+ > Note: the `sqlalchemy` extra installs SQLAlchemy only. Alembic is not a t-sql dependency — if you use alembic autogenerate, add `alembic` to your own project's dependencies (it's your migration tooling, not ours).
652
+
582
653
  ### Two Ways to Define Columns
583
654
 
584
655
  **1. Simple Column annotations** (for query builder only):
@@ -1,13 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: t-sql
3
- Version: 4.12.0
4
- Summary: Safe SQL. SQL queries for python t-strings (PEP 750)
5
- Project-URL: Homepage, https://github.com/nhumrich/t-sql
6
- License-File: LICENSE
7
- Requires-Python: >=3.14
8
- Requires-Dist: alembic>=1.17.0
9
- Description-Content-Type: text/markdown
10
-
11
1
  # t-sql
12
2
 
13
3
  A lightweight SQL templating library that leverages Python 3.14's t-strings (PEP 750).
@@ -310,6 +300,31 @@ query = (Posts.select()
310
300
  .left_join(Users, on=Posts.user_id == Users.id))
311
301
  ```
312
302
 
303
+ ### Raw JOIN clauses (escape hatch)
304
+
305
+ For join shapes the typed `join()`/`left_join()`/`right_join()` can't express —
306
+ LATERAL subqueries, ON-less cross joins, set-returning functions — use
307
+ `join_raw()` to splice a complete JOIN clause Template verbatim. The Template
308
+ must carry the **whole** clause including the join keyword; nothing is added
309
+ around it. Parameters inside the Template are still parameterized and renumber
310
+ correctly alongside the rest of the query.
311
+
312
+ ```python
313
+ query = (SelectQueryBuilder.from_table('records', schema='dataset', alias='t')
314
+ .select(t't.*')
315
+ .join_raw(t'CROSS JOIN LATERAL unnest(t.tags) AS tag'))
316
+
317
+ # Parameterized raw join, composed with a WHERE clause
318
+ query = (SelectQueryBuilder.from_table('records', alias='t')
319
+ .select(t't.*')
320
+ .join_raw(t'LEFT JOIN other o ON o.id = t.ref AND o.kind = {"x"}')
321
+ .where(t't.active = {True}'))
322
+ ```
323
+
324
+ > ⚠️ **Not advised.** `join_raw()` bypasses the builder's join structure. Prefer
325
+ > the typed `join()`/`left_join()`/`right_join()` with a Table + Condition
326
+ > wherever they suffice.
327
+
313
328
  ## Query Features
314
329
 
315
330
  ### Selecting All Columns from a Table
@@ -333,6 +348,34 @@ query = Posts.select(Posts.ALL, Users.ALL).join(Users, Posts.user_id == Users.id
333
348
 
334
349
  This is particularly useful when joining tables where you want all columns from one table but only specific columns from others.
335
350
 
351
+ ### DISTINCT ON (PostgreSQL)
352
+
353
+ `distinct_on()` emits a `SELECT DISTINCT ON (...)` clause. It accepts Column
354
+ objects, string column names, or raw Templates — coerced exactly like
355
+ `select()`.
356
+
357
+ ```python
358
+ query = Users.select(Users.id, Users.username).distinct_on('username')
359
+ # ('SELECT DISTINCT ON (username) users.id, users.username FROM users', [])
360
+
361
+ # Multiple columns are comma-joined inside the parens
362
+ query = Users.select(Users.id).distinct_on('username', 'email')
363
+ # ('SELECT DISTINCT ON (username, email) users.id FROM users', [])
364
+ ```
365
+
366
+ ### FROM ONLY and Table Aliases
367
+
368
+ When building from a string table name, `from_table()` accepts `only=True` to
369
+ emit `FROM ONLY {table}` (excludes inheriting child tables in PostgreSQL) and
370
+ `alias=...` to emit `FROM {table} AS {alias}` (the alias is identifier-validated):
371
+
372
+ ```python
373
+ query = (SelectQueryBuilder.from_table('records', schema='dataset', alias='t', only=True)
374
+ .select(t't.*')
375
+ .where(t't.active = {True}'))
376
+ # ('SELECT t.* FROM ONLY dataset.records AS t WHERE (t.active = $1)', [True])
377
+ ```
378
+
336
379
  ### NULL Checks and Other Operators
337
380
 
338
381
  ```python
@@ -359,6 +402,11 @@ query = Posts.select().order_by(Posts.id) # defaults to ASC
359
402
  query = Posts.select().order_by(Posts.id.desc())
360
403
  query = Posts.select().order_by(Posts.created_at.asc(), Posts.id.desc())
361
404
 
405
+ # ORDER BY / GROUP BY also accept raw Templates, emitted verbatim
406
+ # (parity with where()/having()/select()), for computed expressions:
407
+ query = Posts.select().order_by(t'lower(title) DESC')
408
+ query = Posts.select().group_by(t"date_trunc('day', created_at)")
409
+
362
410
  # LIMIT and OFFSET
363
411
  query = Posts.select().limit(10).offset(20)
364
412
 
@@ -589,6 +637,8 @@ pip install t-sql[sqlalchemy]
589
637
  uv add t-sql --optional sqlalchemy
590
638
  ```
591
639
 
640
+ > Note: the `sqlalchemy` extra installs SQLAlchemy only. Alembic is not a t-sql dependency — if you use alembic autogenerate, add `alembic` to your own project's dependencies (it's your migration tooling, not ours).
641
+
592
642
  ### Two Ways to Define Columns
593
643
 
594
644
  **1. Simple Column annotations** (for query builder only):
@@ -4,13 +4,14 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "t-sql"
7
- version = "4.12.0"
7
+ version = "4.13.0"
8
8
  description = "Safe SQL. SQL queries for python t-strings (PEP 750)"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.14"
11
- dependencies = [
12
- "alembic>=1.17.0",
13
- ]
11
+ dependencies = []
12
+
13
+ [project.optional-dependencies]
14
+ sqlalchemy = ["sqlalchemy>=2.0.0"]
14
15
 
15
16
  [project.urls]
16
17
  Homepage = "https://github.com/nhumrich/t-sql"
@@ -20,6 +21,7 @@ Homepage = "https://github.com/nhumrich/t-sql"
20
21
  dev = [
21
22
  "aiomysql>=0.2.0",
22
23
  "aiosqlite>=0.20.0",
24
+ "alembic>=1.17.0",
23
25
  "anyio>=4.9.0",
24
26
  "asyncpg>=0.30.0",
25
27
  "cryptography>=46.0.2",
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes