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.
- {t_sql-2.1.2 → t_sql-2.1.3}/PKG-INFO +38 -8
- {t_sql-2.1.2 → t_sql-2.1.3}/README.md +37 -7
- {t_sql-2.1.2 → t_sql-2.1.3}/pyproject.toml +1 -1
- {t_sql-2.1.2 → t_sql-2.1.3}/tsql/__init__.py +56 -2
- {t_sql-2.1.2 → t_sql-2.1.3}/.dockerignore +0 -0
- {t_sql-2.1.2 → t_sql-2.1.3}/.github/workflows/publish.yml +0 -0
- {t_sql-2.1.2 → t_sql-2.1.3}/.github/workflows/test.yml +0 -0
- {t_sql-2.1.2 → t_sql-2.1.3}/.gitignore +0 -0
- {t_sql-2.1.2 → t_sql-2.1.3}/Dockerfile +0 -0
- {t_sql-2.1.2 → t_sql-2.1.3}/LICENSE +0 -0
- {t_sql-2.1.2 → t_sql-2.1.3}/compose.yaml +0 -0
- {t_sql-2.1.2 → t_sql-2.1.3}/context7.json +0 -0
- {t_sql-2.1.2 → t_sql-2.1.3}/pytest.ini +0 -0
- {t_sql-2.1.2 → t_sql-2.1.3}/tests/test_alembic_integration.py +0 -0
- {t_sql-2.1.2 → t_sql-2.1.3}/tests/test_asyncpg_integration.py +0 -0
- {t_sql-2.1.2 → t_sql-2.1.3}/tests/test_different_object_types.py +0 -0
- {t_sql-2.1.2 → t_sql-2.1.3}/tests/test_escaped.py +0 -0
- {t_sql-2.1.2 → t_sql-2.1.3}/tests/test_escaped_binary_hex.py +0 -0
- {t_sql-2.1.2 → t_sql-2.1.3}/tests/test_helper_functions.py +0 -0
- {t_sql-2.1.2 → t_sql-2.1.3}/tests/test_injection_edge_cases.py +0 -0
- {t_sql-2.1.2 → t_sql-2.1.3}/tests/test_injection_protection_validation.py +0 -0
- {t_sql-2.1.2 → t_sql-2.1.3}/tests/test_injections_for_escaped.py +0 -0
- {t_sql-2.1.2 → t_sql-2.1.3}/tests/test_mysql_integration.py +0 -0
- {t_sql-2.1.2 → t_sql-2.1.3}/tests/test_query_builder.py +0 -0
- {t_sql-2.1.2 → t_sql-2.1.3}/tests/test_sqlalchemy_integration.py +0 -0
- {t_sql-2.1.2 → t_sql-2.1.3}/tests/test_sqlite_integration.py +0 -0
- {t_sql-2.1.2 → t_sql-2.1.3}/tests/test_styles.py +0 -0
- {t_sql-2.1.2 → t_sql-2.1.3}/tests/test_tsql.py +0 -0
- {t_sql-2.1.2 → t_sql-2.1.3}/tsql/query_builder.py +0 -0
- {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.
|
|
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
|
-
#
|
|
518
|
+
# Rendering Queries
|
|
519
519
|
|
|
520
|
-
|
|
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
|
-
|
|
527
|
-
|
|
528
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
508
|
+
# Rendering Queries
|
|
509
509
|
|
|
510
|
-
|
|
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
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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
|
-
|
|
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.
|
|
@@ -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
|
-
|
|
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
|
|
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
|