t-sql 3.2.0__tar.gz → 4.0.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.
- {t_sql-3.2.0 → t_sql-4.0.0}/PKG-INFO +36 -1
- {t_sql-3.2.0 → t_sql-4.0.0}/README.md +35 -0
- {t_sql-3.2.0 → t_sql-4.0.0}/pyproject.toml +1 -1
- {t_sql-3.2.0 → t_sql-4.0.0}/tests/test_query_builder.py +67 -9
- {t_sql-3.2.0 → t_sql-4.0.0}/tests/test_sqlalchemy_integration.py +32 -0
- {t_sql-3.2.0 → t_sql-4.0.0}/tsql/query_builder.py +37 -12
- {t_sql-3.2.0 → t_sql-4.0.0}/.dockerignore +0 -0
- {t_sql-3.2.0 → t_sql-4.0.0}/.github/workflows/publish.yml +0 -0
- {t_sql-3.2.0 → t_sql-4.0.0}/.github/workflows/test.yml +0 -0
- {t_sql-3.2.0 → t_sql-4.0.0}/.gitignore +0 -0
- {t_sql-3.2.0 → t_sql-4.0.0}/Dockerfile +0 -0
- {t_sql-3.2.0 → t_sql-4.0.0}/LICENSE +0 -0
- {t_sql-3.2.0 → t_sql-4.0.0}/compose.yaml +0 -0
- {t_sql-3.2.0 → t_sql-4.0.0}/context7.json +0 -0
- {t_sql-3.2.0 → t_sql-4.0.0}/pytest.ini +0 -0
- {t_sql-3.2.0 → t_sql-4.0.0}/tests/test_alembic_integration.py +0 -0
- {t_sql-3.2.0 → t_sql-4.0.0}/tests/test_asyncpg_integration.py +0 -0
- {t_sql-3.2.0 → t_sql-4.0.0}/tests/test_different_object_types.py +0 -0
- {t_sql-3.2.0 → t_sql-4.0.0}/tests/test_escaped.py +0 -0
- {t_sql-3.2.0 → t_sql-4.0.0}/tests/test_escaped_binary_hex.py +0 -0
- {t_sql-3.2.0 → t_sql-4.0.0}/tests/test_helper_functions.py +0 -0
- {t_sql-3.2.0 → t_sql-4.0.0}/tests/test_injection_edge_cases.py +0 -0
- {t_sql-3.2.0 → t_sql-4.0.0}/tests/test_injection_protection_validation.py +0 -0
- {t_sql-3.2.0 → t_sql-4.0.0}/tests/test_injections_for_escaped.py +0 -0
- {t_sql-3.2.0 → t_sql-4.0.0}/tests/test_mysql_integration.py +0 -0
- {t_sql-3.2.0 → t_sql-4.0.0}/tests/test_parameter_names.py +0 -0
- {t_sql-3.2.0 → t_sql-4.0.0}/tests/test_sqlite_integration.py +0 -0
- {t_sql-3.2.0 → t_sql-4.0.0}/tests/test_styles.py +0 -0
- {t_sql-3.2.0 → t_sql-4.0.0}/tests/test_tsql.py +0 -0
- {t_sql-3.2.0 → t_sql-4.0.0}/tsql/__init__.py +0 -0
- {t_sql-3.2.0 → t_sql-4.0.0}/tsql/styles.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: t-sql
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.0.0
|
|
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
|
|
@@ -257,12 +257,47 @@ query = (Posts.select()
|
|
|
257
257
|
|
|
258
258
|
## Query Features
|
|
259
259
|
|
|
260
|
+
### Selecting All Columns from a Table
|
|
261
|
+
|
|
262
|
+
Use `Table.ALL` to select all columns from a specific table:
|
|
263
|
+
|
|
260
264
|
```python
|
|
265
|
+
# Select all columns from posts
|
|
266
|
+
query = Posts.select(Posts.ALL)
|
|
267
|
+
# ('SELECT posts.* FROM posts', [])
|
|
268
|
+
|
|
269
|
+
# Select all columns from posts + specific columns from joined tables
|
|
270
|
+
query = (Posts.select(Posts.ALL, Users.username, Users.email)
|
|
271
|
+
.join(Users, Posts.user_id == Users.id))
|
|
272
|
+
# ('SELECT posts.*, users.username, users.email FROM posts INNER JOIN users ON ...', [])
|
|
273
|
+
|
|
274
|
+
# Select all columns from multiple tables
|
|
275
|
+
query = Posts.select(Posts.ALL, Users.ALL).join(Users, Posts.user_id == Users.id)
|
|
276
|
+
# ('SELECT posts.*, users.* FROM posts INNER JOIN users ON ...', [])
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
This is particularly useful when joining tables where you want all columns from one table but only specific columns from others.
|
|
280
|
+
|
|
281
|
+
### NULL Checks and Other Operators
|
|
282
|
+
|
|
283
|
+
```python
|
|
284
|
+
# NULL checks
|
|
285
|
+
query = Users.select().where(Users.email.is_null())
|
|
286
|
+
query = Users.select().where(Users.email.is_not_null())
|
|
287
|
+
|
|
261
288
|
# IN clause
|
|
262
289
|
query = Users.select().where(Users.id.in_([1, 2, 3]))
|
|
290
|
+
query = Users.select().where(Users.id.not_in([1, 2, 3]))
|
|
263
291
|
|
|
264
292
|
# LIKE clause
|
|
265
293
|
query = Users.select().where(Users.username.like('%john%'))
|
|
294
|
+
query = Users.select().where(Users.username.not_like('%john%'))
|
|
295
|
+
query = Users.select().where(Users.username.ilike('%JOHN%')) # case-insensitive
|
|
296
|
+
query = Users.select().where(Users.username.not_ilike('%JOHN%'))
|
|
297
|
+
|
|
298
|
+
# BETWEEN clause
|
|
299
|
+
query = Users.select().where(Users.age.between(18, 65))
|
|
300
|
+
query = Users.select().where(Users.age.not_between(18, 65))
|
|
266
301
|
|
|
267
302
|
# ORDER BY
|
|
268
303
|
query = Posts.select().order_by(Posts.id) # defaults to ASC
|
|
@@ -247,12 +247,47 @@ query = (Posts.select()
|
|
|
247
247
|
|
|
248
248
|
## Query Features
|
|
249
249
|
|
|
250
|
+
### Selecting All Columns from a Table
|
|
251
|
+
|
|
252
|
+
Use `Table.ALL` to select all columns from a specific table:
|
|
253
|
+
|
|
250
254
|
```python
|
|
255
|
+
# Select all columns from posts
|
|
256
|
+
query = Posts.select(Posts.ALL)
|
|
257
|
+
# ('SELECT posts.* FROM posts', [])
|
|
258
|
+
|
|
259
|
+
# Select all columns from posts + specific columns from joined tables
|
|
260
|
+
query = (Posts.select(Posts.ALL, Users.username, Users.email)
|
|
261
|
+
.join(Users, Posts.user_id == Users.id))
|
|
262
|
+
# ('SELECT posts.*, users.username, users.email FROM posts INNER JOIN users ON ...', [])
|
|
263
|
+
|
|
264
|
+
# Select all columns from multiple tables
|
|
265
|
+
query = Posts.select(Posts.ALL, Users.ALL).join(Users, Posts.user_id == Users.id)
|
|
266
|
+
# ('SELECT posts.*, users.* FROM posts INNER JOIN users ON ...', [])
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
This is particularly useful when joining tables where you want all columns from one table but only specific columns from others.
|
|
270
|
+
|
|
271
|
+
### NULL Checks and Other Operators
|
|
272
|
+
|
|
273
|
+
```python
|
|
274
|
+
# NULL checks
|
|
275
|
+
query = Users.select().where(Users.email.is_null())
|
|
276
|
+
query = Users.select().where(Users.email.is_not_null())
|
|
277
|
+
|
|
251
278
|
# IN clause
|
|
252
279
|
query = Users.select().where(Users.id.in_([1, 2, 3]))
|
|
280
|
+
query = Users.select().where(Users.id.not_in([1, 2, 3]))
|
|
253
281
|
|
|
254
282
|
# LIKE clause
|
|
255
283
|
query = Users.select().where(Users.username.like('%john%'))
|
|
284
|
+
query = Users.select().where(Users.username.not_like('%john%'))
|
|
285
|
+
query = Users.select().where(Users.username.ilike('%JOHN%')) # case-insensitive
|
|
286
|
+
query = Users.select().where(Users.username.not_ilike('%JOHN%'))
|
|
287
|
+
|
|
288
|
+
# BETWEEN clause
|
|
289
|
+
query = Users.select().where(Users.age.between(18, 65))
|
|
290
|
+
query = Users.select().where(Users.age.not_between(18, 65))
|
|
256
291
|
|
|
257
292
|
# ORDER BY
|
|
258
293
|
query = Posts.select().order_by(Posts.id) # defaults to ASC
|
|
@@ -838,23 +838,81 @@ def test_tstring_with_params_in_select():
|
|
|
838
838
|
assert params == [' - ', 5]
|
|
839
839
|
|
|
840
840
|
|
|
841
|
-
def
|
|
842
|
-
"""Test
|
|
843
|
-
|
|
841
|
+
def test_select_table_all():
|
|
842
|
+
"""Test selecting all columns from a table using Table.ALL"""
|
|
843
|
+
query = Posts.select(Posts.ALL)
|
|
844
|
+
sql, params = query.render()
|
|
845
|
+
|
|
846
|
+
assert 'SELECT posts.*' in sql
|
|
847
|
+
assert 'FROM posts' in sql
|
|
848
|
+
assert params == []
|
|
849
|
+
|
|
850
|
+
|
|
851
|
+
def test_select_table_all_with_other_columns():
|
|
852
|
+
"""Test mixing Table.ALL with specific columns from another table"""
|
|
853
|
+
query = Posts.select(Posts.ALL, Users.username, Users.email)
|
|
854
|
+
sql, params = query.render()
|
|
855
|
+
|
|
856
|
+
assert 'SELECT posts.*, users.username, users.email' in sql
|
|
857
|
+
assert 'FROM posts' in sql
|
|
858
|
+
assert params == []
|
|
859
|
+
|
|
860
|
+
|
|
861
|
+
def test_select_multiple_table_alls():
|
|
862
|
+
"""Test selecting all columns from multiple tables"""
|
|
863
|
+
query = Posts.select(Posts.ALL, Users.ALL)
|
|
864
|
+
sql, params = query.render()
|
|
865
|
+
|
|
866
|
+
assert 'SELECT posts.*, users.*' in sql
|
|
867
|
+
assert 'FROM posts' in sql
|
|
868
|
+
assert params == []
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
def test_select_table_all_with_join():
|
|
872
|
+
"""Test Table.ALL in a real-world join scenario"""
|
|
873
|
+
query = (Posts.select(Posts.ALL, Users.username)
|
|
874
|
+
.join(Users, Posts.user_id == Users.id)
|
|
875
|
+
.where(Posts.id > 100))
|
|
876
|
+
sql, params = query.render()
|
|
877
|
+
|
|
878
|
+
assert 'SELECT posts.*, users.username' in sql
|
|
879
|
+
assert 'FROM posts' in sql
|
|
880
|
+
assert 'INNER JOIN users ON posts.user_id = users.id' in sql
|
|
881
|
+
assert 'WHERE posts.id > ?' in sql
|
|
882
|
+
assert params == [100]
|
|
883
|
+
|
|
884
|
+
|
|
885
|
+
def test_select_table_all_with_schema():
|
|
886
|
+
"""Test that Table.ALL works with schema-qualified tables"""
|
|
887
|
+
class Accounts(Table, table_name='accounts', schema='other'):
|
|
888
|
+
id: Column
|
|
889
|
+
name: Column
|
|
890
|
+
|
|
891
|
+
query = Accounts.select(Accounts.ALL)
|
|
892
|
+
sql, params = query.render()
|
|
893
|
+
|
|
894
|
+
assert 'SELECT other.accounts.*' in sql
|
|
895
|
+
assert 'FROM other.accounts' in sql
|
|
896
|
+
assert params == []
|
|
897
|
+
|
|
898
|
+
|
|
899
|
+
def test_column_is_null_method():
|
|
900
|
+
"""Test is_null method"""
|
|
901
|
+
condition = Users.email.is_null()
|
|
844
902
|
assert condition.operator == 'IS'
|
|
845
903
|
assert condition.right is None
|
|
846
904
|
|
|
847
905
|
|
|
848
|
-
def
|
|
849
|
-
"""Test is_not_null
|
|
850
|
-
condition = Users.email.is_not_null
|
|
906
|
+
def test_column_is_not_null_method():
|
|
907
|
+
"""Test is_not_null method"""
|
|
908
|
+
condition = Users.email.is_not_null()
|
|
851
909
|
assert condition.operator == 'IS NOT'
|
|
852
910
|
assert condition.right is None
|
|
853
911
|
|
|
854
912
|
|
|
855
913
|
def test_where_with_is_null():
|
|
856
914
|
"""Test WHERE with is_null"""
|
|
857
|
-
query = Users.select().where(Users.email.is_null)
|
|
915
|
+
query = Users.select().where(Users.email.is_null())
|
|
858
916
|
sql, params = query.render()
|
|
859
917
|
|
|
860
918
|
assert 'WHERE users.email IS NULL' in sql
|
|
@@ -863,7 +921,7 @@ def test_where_with_is_null():
|
|
|
863
921
|
|
|
864
922
|
def test_where_with_is_not_null():
|
|
865
923
|
"""Test WHERE with is_not_null"""
|
|
866
|
-
query = Users.select().where(Users.email.is_not_null)
|
|
924
|
+
query = Users.select().where(Users.email.is_not_null())
|
|
867
925
|
sql, params = query.render()
|
|
868
926
|
|
|
869
927
|
assert 'WHERE users.email IS NOT NULL' in sql
|
|
@@ -1002,7 +1060,7 @@ def test_complex_query_with_new_operators():
|
|
|
1002
1060
|
"""Test complex query using multiple new operators"""
|
|
1003
1061
|
query = (Users.select()
|
|
1004
1062
|
.where(Users.id.between(1, 100))
|
|
1005
|
-
.where(Users.email.is_not_null)
|
|
1063
|
+
.where(Users.email.is_not_null())
|
|
1006
1064
|
.where(Users.id.not_in([5, 10, 15]))
|
|
1007
1065
|
.where(Users.username.ilike('%test%')))
|
|
1008
1066
|
sql, params = query.render()
|
|
@@ -229,6 +229,38 @@ def test_mixing_query_builder_with_tsql():
|
|
|
229
229
|
assert params == [18, 'john', 'john', 25]
|
|
230
230
|
|
|
231
231
|
|
|
232
|
+
def test_sa_column_annotations_are_correct_type():
|
|
233
|
+
"""Test that SA Column assignments get correct type annotations for IDE autocomplete"""
|
|
234
|
+
from tsql.query_builder import Column as TsqlColumn
|
|
235
|
+
|
|
236
|
+
metadata = MetaData()
|
|
237
|
+
|
|
238
|
+
class MyTable(Table, table_name='mytable', metadata=metadata):
|
|
239
|
+
my_column = Column(TIMESTAMP())
|
|
240
|
+
another = Column(Integer())
|
|
241
|
+
text_field = Column(String(100))
|
|
242
|
+
|
|
243
|
+
# Verify that __annotations__ has been updated to reflect tsql.Column
|
|
244
|
+
assert 'my_column' in MyTable.__annotations__
|
|
245
|
+
assert 'another' in MyTable.__annotations__
|
|
246
|
+
assert 'text_field' in MyTable.__annotations__
|
|
247
|
+
|
|
248
|
+
assert MyTable.__annotations__['my_column'] == TsqlColumn
|
|
249
|
+
assert MyTable.__annotations__['another'] == TsqlColumn
|
|
250
|
+
assert MyTable.__annotations__['text_field'] == TsqlColumn
|
|
251
|
+
|
|
252
|
+
# Verify that the columns actually work as tsql.Column objects
|
|
253
|
+
col = MyTable.my_column
|
|
254
|
+
assert isinstance(col, TsqlColumn)
|
|
255
|
+
assert hasattr(col, 'is_null')
|
|
256
|
+
assert hasattr(col, 'asc')
|
|
257
|
+
assert hasattr(col, 'desc')
|
|
258
|
+
|
|
259
|
+
# Verify is_null works
|
|
260
|
+
condition = col.is_null()
|
|
261
|
+
assert condition.operator == 'IS'
|
|
262
|
+
assert condition.right is None
|
|
263
|
+
|
|
232
264
|
def gen_id(prefix):
|
|
233
265
|
"""Dummy function for test"""
|
|
234
266
|
return f"{prefix}_123"
|
|
@@ -38,10 +38,9 @@ class OrderByClause:
|
|
|
38
38
|
class Column:
|
|
39
39
|
"""Represents a bound column (table + column name) for building queries"""
|
|
40
40
|
|
|
41
|
-
def __init__(self, table_name: str = None, column_name: str = None,
|
|
41
|
+
def __init__(self, table_name: str = None, column_name: str = None, alias: str = None, schema: str = None):
|
|
42
42
|
self.table_name = table_name
|
|
43
43
|
self.column_name = column_name
|
|
44
|
-
self.python_type = python_type
|
|
45
44
|
self.alias = alias
|
|
46
45
|
self.schema = schema
|
|
47
46
|
|
|
@@ -71,7 +70,7 @@ class Column:
|
|
|
71
70
|
Example:
|
|
72
71
|
users.select(users.first_name.as_('first'), users.last_name.as_('last'))
|
|
73
72
|
"""
|
|
74
|
-
return Column(self.table_name, self.column_name,
|
|
73
|
+
return Column(self.table_name, self.column_name, alias, self.schema)
|
|
75
74
|
|
|
76
75
|
def __eq__(self, other) -> 'Condition':
|
|
77
76
|
if other is None:
|
|
@@ -149,12 +148,10 @@ class Column:
|
|
|
149
148
|
"""
|
|
150
149
|
return Condition(self, 'NOT BETWEEN', (start, end))
|
|
151
150
|
|
|
152
|
-
@property
|
|
153
151
|
def is_null(self) -> 'Condition':
|
|
154
152
|
"""Create an IS NULL condition"""
|
|
155
153
|
return Condition(self, 'IS', None)
|
|
156
154
|
|
|
157
|
-
@property
|
|
158
155
|
def is_not_null(self) -> 'Condition':
|
|
159
156
|
"""Create an IS NOT NULL condition"""
|
|
160
157
|
return Condition(self, 'IS NOT', None)
|
|
@@ -271,7 +268,11 @@ class Table:
|
|
|
271
268
|
sa_columns.append(sa_col)
|
|
272
269
|
|
|
273
270
|
# Create query builder ColumnDescriptor
|
|
274
|
-
setattr(cls, field_name, ColumnDescriptor(field_name
|
|
271
|
+
setattr(cls, field_name, ColumnDescriptor(field_name))
|
|
272
|
+
# Update annotation to reflect the descriptor's return type
|
|
273
|
+
if not hasattr(cls, '__annotations__'):
|
|
274
|
+
cls.__annotations__ = {}
|
|
275
|
+
cls.__annotations__[field_name] = Column
|
|
275
276
|
continue
|
|
276
277
|
|
|
277
278
|
# Check if it's a Column instance (for column_name remapping)
|
|
@@ -283,7 +284,11 @@ class Table:
|
|
|
283
284
|
db_column_name = field_name
|
|
284
285
|
|
|
285
286
|
# Create query builder ColumnDescriptor with the DB column name
|
|
286
|
-
setattr(cls, field_name, ColumnDescriptor(db_column_name
|
|
287
|
+
setattr(cls, field_name, ColumnDescriptor(db_column_name))
|
|
288
|
+
# Update annotation to reflect the descriptor's return type
|
|
289
|
+
if not hasattr(cls, '__annotations__'):
|
|
290
|
+
cls.__annotations__ = {}
|
|
291
|
+
cls.__annotations__[field_name] = Column
|
|
287
292
|
|
|
288
293
|
# Create SQLAlchemy column if metadata provided
|
|
289
294
|
if metadata is not None and HAS_SQLALCHEMY:
|
|
@@ -294,7 +299,11 @@ class Table:
|
|
|
294
299
|
# Check if it's an Ellipsis (...) declaration
|
|
295
300
|
if field_value is ...:
|
|
296
301
|
# Create query builder ColumnDescriptor
|
|
297
|
-
setattr(cls, field_name, ColumnDescriptor(field_name
|
|
302
|
+
setattr(cls, field_name, ColumnDescriptor(field_name))
|
|
303
|
+
# Update annotation to reflect the descriptor's return type
|
|
304
|
+
if not hasattr(cls, '__annotations__'):
|
|
305
|
+
cls.__annotations__ = {}
|
|
306
|
+
cls.__annotations__[field_name] = Column
|
|
298
307
|
continue
|
|
299
308
|
|
|
300
309
|
# Otherwise, handle type annotations
|
|
@@ -303,7 +312,11 @@ class Table:
|
|
|
303
312
|
continue
|
|
304
313
|
|
|
305
314
|
# Create query builder ColumnDescriptor for type-annotated fields
|
|
306
|
-
setattr(cls, field_name, ColumnDescriptor(field_name
|
|
315
|
+
setattr(cls, field_name, ColumnDescriptor(field_name))
|
|
316
|
+
# Update annotation to reflect the descriptor's return type
|
|
317
|
+
if not hasattr(cls, '__annotations__'):
|
|
318
|
+
cls.__annotations__ = {}
|
|
319
|
+
cls.__annotations__[field_name] = Column
|
|
307
320
|
|
|
308
321
|
# Create SQLAlchemy column if metadata provided
|
|
309
322
|
if metadata is not None and HAS_SQLALCHEMY:
|
|
@@ -314,6 +327,9 @@ class Table:
|
|
|
314
327
|
if metadata is not None and HAS_SQLALCHEMY:
|
|
315
328
|
cls._sa_table = SATable(cls.table_name, metadata, *sa_columns, schema=schema)
|
|
316
329
|
|
|
330
|
+
# Add the ALL descriptor for wildcard column selection
|
|
331
|
+
cls.ALL = AllColumnsDescriptor()
|
|
332
|
+
|
|
317
333
|
@classmethod
|
|
318
334
|
def select(cls, *columns: Union['Column', Template]) -> 'SelectQueryBuilder':
|
|
319
335
|
"""Start building a SELECT query"""
|
|
@@ -367,9 +383,8 @@ class Table:
|
|
|
367
383
|
class ColumnDescriptor:
|
|
368
384
|
"""Descriptor that creates Column objects when accessed on Table classes or instances"""
|
|
369
385
|
|
|
370
|
-
def __init__(self, column_name: str
|
|
386
|
+
def __init__(self, column_name: str):
|
|
371
387
|
self.column_name = column_name
|
|
372
|
-
self.python_type = python_type
|
|
373
388
|
|
|
374
389
|
def __set_name__(self, owner, name):
|
|
375
390
|
self.column_name = name
|
|
@@ -378,7 +393,17 @@ class ColumnDescriptor:
|
|
|
378
393
|
if objtype is None:
|
|
379
394
|
objtype = type(obj)
|
|
380
395
|
schema = getattr(objtype, 'schema', None)
|
|
381
|
-
return Column(objtype.table_name, self.column_name,
|
|
396
|
+
return Column(objtype.table_name, self.column_name, schema=schema)
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
class AllColumnsDescriptor:
|
|
400
|
+
"""Descriptor that creates a Column with wildcard (*) for selecting all columns from a table"""
|
|
401
|
+
|
|
402
|
+
def __get__(self, obj, objtype=None) -> Column:
|
|
403
|
+
if objtype is None:
|
|
404
|
+
objtype = type(obj)
|
|
405
|
+
schema = getattr(objtype, 'schema', None)
|
|
406
|
+
return Column(objtype.table_name, '*', schema=schema)
|
|
382
407
|
|
|
383
408
|
|
|
384
409
|
class Condition:
|
|
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
|