t-sql 2.1.2__tar.gz → 2.1.3__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 (30) hide show
  1. {t_sql-2.1.2 → t_sql-2.1.3}/PKG-INFO +38 -8
  2. {t_sql-2.1.2 → t_sql-2.1.3}/README.md +37 -7
  3. {t_sql-2.1.2 → t_sql-2.1.3}/pyproject.toml +1 -1
  4. {t_sql-2.1.2 → t_sql-2.1.3}/tsql/__init__.py +56 -2
  5. {t_sql-2.1.2 → t_sql-2.1.3}/.dockerignore +0 -0
  6. {t_sql-2.1.2 → t_sql-2.1.3}/.github/workflows/publish.yml +0 -0
  7. {t_sql-2.1.2 → t_sql-2.1.3}/.github/workflows/test.yml +0 -0
  8. {t_sql-2.1.2 → t_sql-2.1.3}/.gitignore +0 -0
  9. {t_sql-2.1.2 → t_sql-2.1.3}/Dockerfile +0 -0
  10. {t_sql-2.1.2 → t_sql-2.1.3}/LICENSE +0 -0
  11. {t_sql-2.1.2 → t_sql-2.1.3}/compose.yaml +0 -0
  12. {t_sql-2.1.2 → t_sql-2.1.3}/context7.json +0 -0
  13. {t_sql-2.1.2 → t_sql-2.1.3}/pytest.ini +0 -0
  14. {t_sql-2.1.2 → t_sql-2.1.3}/tests/test_alembic_integration.py +0 -0
  15. {t_sql-2.1.2 → t_sql-2.1.3}/tests/test_asyncpg_integration.py +0 -0
  16. {t_sql-2.1.2 → t_sql-2.1.3}/tests/test_different_object_types.py +0 -0
  17. {t_sql-2.1.2 → t_sql-2.1.3}/tests/test_escaped.py +0 -0
  18. {t_sql-2.1.2 → t_sql-2.1.3}/tests/test_escaped_binary_hex.py +0 -0
  19. {t_sql-2.1.2 → t_sql-2.1.3}/tests/test_helper_functions.py +0 -0
  20. {t_sql-2.1.2 → t_sql-2.1.3}/tests/test_injection_edge_cases.py +0 -0
  21. {t_sql-2.1.2 → t_sql-2.1.3}/tests/test_injection_protection_validation.py +0 -0
  22. {t_sql-2.1.2 → t_sql-2.1.3}/tests/test_injections_for_escaped.py +0 -0
  23. {t_sql-2.1.2 → t_sql-2.1.3}/tests/test_mysql_integration.py +0 -0
  24. {t_sql-2.1.2 → t_sql-2.1.3}/tests/test_query_builder.py +0 -0
  25. {t_sql-2.1.2 → t_sql-2.1.3}/tests/test_sqlalchemy_integration.py +0 -0
  26. {t_sql-2.1.2 → t_sql-2.1.3}/tests/test_sqlite_integration.py +0 -0
  27. {t_sql-2.1.2 → t_sql-2.1.3}/tests/test_styles.py +0 -0
  28. {t_sql-2.1.2 → t_sql-2.1.3}/tests/test_tsql.py +0 -0
  29. {t_sql-2.1.2 → t_sql-2.1.3}/tsql/query_builder.py +0 -0
  30. {t_sql-2.1.2 → t_sql-2.1.3}/tsql/styles.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: t-sql
3
- Version: 2.1.2
3
+ Version: 2.1.3
4
4
  Summary: Safe SQL. SQL queries for python t-strings (PEP 750)
5
5
  Project-URL: Homepage, https://github.com/nhumrich/t-sql
6
6
  License-File: LICENSE
@@ -515,17 +515,47 @@ class Users(Table, table_name='user_accounts', schema='public'):
515
515
  name: Column
516
516
  ```
517
517
 
518
- # Note on Usage
518
+ # Rendering Queries
519
519
 
520
- This library should ideally be used in middleware or library code right before making a query. It can enforce the use of t-strings and prevent raw strings:
520
+ All query types (t-strings, TSQL objects, and QueryBuilder objects) can be rendered using `tsql.render()`:
521
521
 
522
522
  ```python
523
- from string.templatelib import Template
524
523
  import tsql
524
+ from tsql.query_builder import Table, Column
525
+
526
+ class Users(Table):
527
+ id: Column
528
+ name: Column
529
+
530
+ # All of these work with tsql.render():
531
+ sql, params = tsql.render(t"SELECT * FROM users WHERE id = {user_id}")
532
+ sql, params = tsql.render(Users.select().where(Users.id == user_id))
533
+ sql, params = tsql.render(tsql.select('users', user_id))
534
+
535
+ # Or call .render() directly on TSQL/QueryBuilder objects:
536
+ query = Users.select().where(Users.age > 18)
537
+ sql, params = query.render()
538
+ ```
539
+
540
+ # Type Safety & Preventing SQL Injection
541
+
542
+ This library should ideally be used in middleware or library code to enforce safe query construction. Use the `TSQLQuery` type to prevent raw strings:
525
543
 
526
- def execute_sql_query(query):
527
- if not isinstance(query, Template):
528
- raise TypeError('Cannot make a query without using t-strings')
544
+ ```python
545
+ from tsql import TSQLQuery, render
546
+
547
+ def execute_sql_query(query: TSQLQuery):
548
+ """Only accepts safe, parameterized queries"""
549
+ sql, params = render(query)
550
+ return sql_engine.execute(sql, params)
529
551
 
530
- return sql_engine.execute(*tsql.render(query))
552
+ # Type checker allows these:
553
+ execute_sql_query(t"SELECT * FROM users WHERE id = {user_id}") # ✓
554
+ execute_sql_query(Users.select()) # ✓
555
+ execute_sql_query(tsql.select('users')) # ✓
556
+
557
+ # Type checker rejects raw strings:
558
+ execute_sql_query("SELECT * FROM users") # ✗ Type error!
531
559
  ```
560
+
561
+ The `TSQLQuery` type is a union of `TSQL`, `Template` (t-strings), and `QueryBuilder`, ensuring all queries are safe from SQL injection.
@@ -505,17 +505,47 @@ class Users(Table, table_name='user_accounts', schema='public'):
505
505
  name: Column
506
506
  ```
507
507
 
508
- # Note on Usage
508
+ # Rendering Queries
509
509
 
510
- This library should ideally be used in middleware or library code right before making a query. It can enforce the use of t-strings and prevent raw strings:
510
+ All query types (t-strings, TSQL objects, and QueryBuilder objects) can be rendered using `tsql.render()`:
511
511
 
512
512
  ```python
513
- from string.templatelib import Template
514
513
  import tsql
514
+ from tsql.query_builder import Table, Column
515
+
516
+ class Users(Table):
517
+ id: Column
518
+ name: Column
519
+
520
+ # All of these work with tsql.render():
521
+ sql, params = tsql.render(t"SELECT * FROM users WHERE id = {user_id}")
522
+ sql, params = tsql.render(Users.select().where(Users.id == user_id))
523
+ sql, params = tsql.render(tsql.select('users', user_id))
524
+
525
+ # Or call .render() directly on TSQL/QueryBuilder objects:
526
+ query = Users.select().where(Users.age > 18)
527
+ sql, params = query.render()
528
+ ```
529
+
530
+ # Type Safety & Preventing SQL Injection
531
+
532
+ This library should ideally be used in middleware or library code to enforce safe query construction. Use the `TSQLQuery` type to prevent raw strings:
515
533
 
516
- def execute_sql_query(query):
517
- if not isinstance(query, Template):
518
- raise TypeError('Cannot make a query without using t-strings')
534
+ ```python
535
+ from tsql import TSQLQuery, render
536
+
537
+ def execute_sql_query(query: TSQLQuery):
538
+ """Only accepts safe, parameterized queries"""
539
+ sql, params = render(query)
540
+ return sql_engine.execute(sql, params)
519
541
 
520
- return sql_engine.execute(*tsql.render(query))
542
+ # Type checker allows these:
543
+ execute_sql_query(t"SELECT * FROM users WHERE id = {user_id}") # ✓
544
+ execute_sql_query(Users.select()) # ✓
545
+ execute_sql_query(tsql.select('users')) # ✓
546
+
547
+ # Type checker rejects raw strings:
548
+ execute_sql_query("SELECT * FROM users") # ✗ Type error!
521
549
  ```
550
+
551
+ The `TSQLQuery` type is a union of `TSQL`, `Template` (t-strings), and `QueryBuilder`, ensuring all queries are safe from SQL injection.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "t-sql"
7
- version = "2.1.2"
7
+ version = "2.1.3"
8
8
  description = "Safe SQL. SQL queries for python t-strings (PEP 750)"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.14"
@@ -1,10 +1,13 @@
1
1
  import re
2
2
  import string
3
- from typing import NamedTuple, Tuple, Any, List, Dict, Iterable
3
+ from typing import NamedTuple, Tuple, Any, List, Dict, Iterable, Union, TYPE_CHECKING
4
4
  from string.templatelib import Template, Interpolation
5
5
 
6
6
  from tsql.styles import ParamStyle, QMARK
7
7
 
8
+ if TYPE_CHECKING:
9
+ from tsql.query_builder import QueryBuilder
10
+
8
11
  default_style = QMARK
9
12
 
10
13
  def set_style(style: type[ParamStyle]):
@@ -213,7 +216,57 @@ def as_set(value_dict: dict[str, Any]):
213
216
  return tsql_obj
214
217
 
215
218
 
216
- def render(query: Template|TSQL, style=None) -> RenderedQuery:
219
+ # Type alias for safe, parameterized queries
220
+ TSQLQuery = Union[TSQL, Template, 'QueryBuilder']
221
+ """Type alias representing safe, parameterized SQL queries.
222
+
223
+ This type includes all valid ways to construct safe queries in tsql:
224
+ - TSQL: Rendered t-string queries
225
+ - Template: Raw t-string templates (PEP 750)
226
+ - QueryBuilder: Type-safe query builder objects
227
+
228
+ Use this type to ensure functions only accept safe, parameterized queries
229
+ and never raw strings that could be vulnerable to SQL injection.
230
+
231
+ Examples:
232
+ Accept any safe query type::
233
+
234
+ def execute_query(query: TSQLQuery) -> None:
235
+ sql, params = render(query)
236
+ cursor.execute(sql, params)
237
+
238
+ Type checking prevents unsafe usage::
239
+
240
+ execute_query(t"SELECT * FROM users WHERE id = {user_id}") # OK
241
+ execute_query(select("users")) # OK
242
+ execute_query(User.select().where(User.id == 1)) # OK
243
+ execute_query("SELECT * FROM users") # Type error!
244
+ """
245
+
246
+
247
+ def render(query: TSQLQuery, style=None) -> RenderedQuery:
248
+ """Render a safe query to SQL and parameters.
249
+
250
+ Args:
251
+ query: A TSQLQuery (TSQL, Template, or QueryBuilder)
252
+ style: Optional parameter style (defaults to global default_style)
253
+
254
+ Returns:
255
+ RenderedQuery with sql string and parameter values
256
+
257
+ Raises:
258
+ TypeError: If query is a raw string (use t-strings instead)
259
+ """
260
+ # Handle QueryBuilder (duck typing to avoid circular import)
261
+ if hasattr(query, 'build') and callable(query.build):
262
+ query = query.build()
263
+
264
+ if isinstance(query, str):
265
+ raise TypeError(
266
+ "Cannot render raw strings - they are vulnerable to SQL injection. "
267
+ "Use t-strings instead: t'SELECT * FROM users WHERE id = {user_id}'"
268
+ )
269
+
217
270
  if not isinstance(query, TSQL):
218
271
  query = TSQL(query)
219
272
 
@@ -305,6 +358,7 @@ def delete(table: str, id: str | int) -> TSQL:
305
358
 
306
359
  __all__ = [
307
360
  'TSQL',
361
+ 'TSQLQuery',
308
362
  'render',
309
363
  't_join',
310
364
  'select',
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