TypeDAL 4.9.1__tar.gz → 4.9.2__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.
- {typedal-4.9.1 → typedal-4.9.2}/CHANGELOG.md +6 -0
- {typedal-4.9.1 → typedal-4.9.2}/PKG-INFO +2 -1
- {typedal-4.9.1 → typedal-4.9.2}/pyproject.toml +1 -0
- {typedal-4.9.1 → typedal-4.9.2}/src/typedal/__about__.py +1 -1
- {typedal-4.9.1 → typedal-4.9.2}/src/typedal/fields.py +14 -0
- {typedal-4.9.1 → typedal-4.9.2}/src/typedal/query_builder.py +44 -6
- {typedal-4.9.1 → typedal-4.9.2}/src/typedal/types.py +4 -2
- typedal-4.9.2/tests/conftest.py +30 -0
- {typedal-4.9.1 → typedal-4.9.2}/tests/test_config.py +0 -17
- {typedal-4.9.1 → typedal-4.9.2}/tests/test_query_builder.py +44 -1
- {typedal-4.9.1 → typedal-4.9.2}/.github/workflows/su6.yml +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/.gitignore +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/.readthedocs.yml +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/README.md +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/coverage.svg +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/docs/10_advanced_apis.md +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/docs/1_getting_started.md +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/docs/2_defining_tables.md +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/docs/3_building_queries.md +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/docs/4_relationships.md +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/docs/5_py4web.md +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/docs/6_migrations.md +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/docs/7_configuration.md +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/docs/8_mixins.md +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/docs/9_memoization.md +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/docs/css/code_blocks.css +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/docs/index.md +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/docs/requirements.txt +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/example_new.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/example_old.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/mkdocs.yml +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/src/typedal/__init__.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/src/typedal/caching.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/src/typedal/cli.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/src/typedal/config.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/src/typedal/constants.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/src/typedal/core.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/src/typedal/define.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/src/typedal/enum_helpers.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/src/typedal/for_py4web.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/src/typedal/for_web2py.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/src/typedal/helpers.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/src/typedal/mixins.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/src/typedal/py.typed +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/src/typedal/relationships.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/src/typedal/rows.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/src/typedal/serializers/as_json.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/src/typedal/serializers/typescript.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/src/typedal/tables.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/src/typedal/web2py_py4web_shared.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/tasks.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/tests/__init__.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/tests/configs/simple.toml +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/tests/configs/valid.env +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/tests/configs/valid.toml +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/tests/py314_tests.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/tests/test_cli.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/tests/test_docs_examples.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/tests/test_helpers.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/tests/test_json.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/tests/test_main.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/tests/test_mixins.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/tests/test_mypy.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/tests/test_orm.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/tests/test_py4web.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/tests/test_relationships.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/tests/test_row.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/tests/test_stats.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/tests/test_table.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/tests/test_typescript.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/tests/test_typing_mypy.md +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/tests/test_typing_pyright.md +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/tests/test_web2py.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/tests/test_xx_others.py +0 -0
- {typedal-4.9.1 → typedal-4.9.2}/tests/timings.py +0 -0
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
<!--next-version-placeholder-->
|
|
4
4
|
|
|
5
|
+
## v4.9.2 (2026-07-02)
|
|
6
|
+
|
|
7
|
+
### Fix
|
|
8
|
+
|
|
9
|
+
* **query-builder:** Normalize field rnames in select options ([`5fe46d9`](https://github.com/trialandsuccess/TypeDAL/commit/5fe46d9b4acd3081ff6e87cf7f9a45b8f4aa876b))
|
|
10
|
+
|
|
5
11
|
## v4.9.1 (2026-07-02)
|
|
6
12
|
|
|
7
13
|
### Fix
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: TypeDAL
|
|
3
|
-
Version: 4.9.
|
|
3
|
+
Version: 4.9.2
|
|
4
4
|
Summary: Typing support for PyDAL
|
|
5
5
|
Project-URL: Documentation, https://typedal.readthedocs.io/
|
|
6
6
|
Project-URL: Issues, https://github.com/trialandsuccess/TypeDAL/issues
|
|
@@ -43,6 +43,7 @@ Requires-Dist: ewok; extra == 'dev'
|
|
|
43
43
|
Requires-Dist: hatch; extra == 'dev'
|
|
44
44
|
Requires-Dist: mkdocs; extra == 'dev'
|
|
45
45
|
Requires-Dist: mkdocs-dracula-theme; extra == 'dev'
|
|
46
|
+
Requires-Dist: psycopg2-binary; extra == 'dev'
|
|
46
47
|
Requires-Dist: pydantic<3; extra == 'dev'
|
|
47
48
|
Requires-Dist: pyright<1.1.400; extra == 'dev'
|
|
48
49
|
Requires-Dist: pytest-typing; extra == 'dev'
|
|
@@ -268,6 +268,20 @@ class TypedField[T_Value](Expression): # pragma: no cover
|
|
|
268
268
|
return t.cast(Expression, self._field.lower())
|
|
269
269
|
|
|
270
270
|
|
|
271
|
+
def rname(field: TypedField[t.Any] | Field) -> str:
|
|
272
|
+
"""
|
|
273
|
+
Return the full rname (table and field).
|
|
274
|
+
"""
|
|
275
|
+
|
|
276
|
+
table = field._table
|
|
277
|
+
inner_field = field._field if isinstance(field, TypedField) else field
|
|
278
|
+
|
|
279
|
+
if not (table and inner_field):
|
|
280
|
+
raise ValueError("missing table or inner field on this 'field'")
|
|
281
|
+
|
|
282
|
+
return "%s.%s" % (table._rname, inner_field._rname)
|
|
283
|
+
|
|
284
|
+
|
|
271
285
|
def is_typed_field(cls: t.Any) -> t.TypeGuard["TypedField[t.Any]"]:
|
|
272
286
|
"""
|
|
273
287
|
Is `cls` an instance or subclass of TypedField?
|
|
@@ -152,7 +152,22 @@ class QueryBuilder[T_MetaInstance: _TypedTable]:
|
|
|
152
152
|
"""
|
|
153
153
|
Return a clone of this builder with permission overrides merged in.
|
|
154
154
|
"""
|
|
155
|
-
return self._extend(permissions=
|
|
155
|
+
return self._extend(permissions=permissions)
|
|
156
|
+
|
|
157
|
+
def _normalize_select_option(
|
|
158
|
+
self, value: str | Field | Expression | bool | t.Iterable[str | Field]
|
|
159
|
+
) -> str | bool | list[str]:
|
|
160
|
+
# currently only used for 'distinct' since orderby, ... are patched by pydal itself in select()
|
|
161
|
+
if isinstance(value, bool):
|
|
162
|
+
return value
|
|
163
|
+
|
|
164
|
+
if isinstance(value, (list, tuple, set)):
|
|
165
|
+
return list(self._normalize_select_option(val) for val in value)
|
|
166
|
+
|
|
167
|
+
if rname := getattr(value, "_rname", None):
|
|
168
|
+
return str(rname)
|
|
169
|
+
|
|
170
|
+
return str(value)
|
|
156
171
|
|
|
157
172
|
def select(self, *fields: t.Any, **options: t.Unpack[SelectKwargs]) -> "QueryBuilder[T_MetaInstance]":
|
|
158
173
|
"""
|
|
@@ -179,6 +194,11 @@ class QueryBuilder[T_MetaInstance: _TypedTable]:
|
|
|
179
194
|
left: othertable.on(query) - do a LEFT JOIN. Using TypeDAL relationships with .join() is recommended!
|
|
180
195
|
cache: cache the query result to speed up repeated queries; e.g. (cache=(cache.ram, 3600), cacheable=True)
|
|
181
196
|
"""
|
|
197
|
+
|
|
198
|
+
for key in ("distinct",):
|
|
199
|
+
if options.get(key):
|
|
200
|
+
options[key] = self._normalize_select_option(options[key])
|
|
201
|
+
|
|
182
202
|
return self._extend(select_args=list(fields), select_kwargs=options)
|
|
183
203
|
|
|
184
204
|
def orderby(self, *fields: OrderBy) -> "QueryBuilder[T_MetaInstance]":
|
|
@@ -272,7 +292,10 @@ class QueryBuilder[T_MetaInstance: _TypedTable]:
|
|
|
272
292
|
return self._extend(overwrite_query=new_query)
|
|
273
293
|
|
|
274
294
|
def _parse_relationships(
|
|
275
|
-
self,
|
|
295
|
+
self,
|
|
296
|
+
fields: t.Iterable[str | t.Type[TypedTable]],
|
|
297
|
+
method: JOIN_OPTIONS = None,
|
|
298
|
+
**update: t.Any,
|
|
276
299
|
) -> dict[str, Relationship[t.Any]]:
|
|
277
300
|
"""
|
|
278
301
|
Parse relationship fields into a dict of base relationships with nested relationships.
|
|
@@ -766,7 +789,11 @@ class QueryBuilder[T_MetaInstance: _TypedTable]:
|
|
|
766
789
|
return joins
|
|
767
790
|
|
|
768
791
|
def _build_inner_joins_recursive(
|
|
769
|
-
self,
|
|
792
|
+
self,
|
|
793
|
+
relation: Relationship[t.Any],
|
|
794
|
+
parent_table: t.Type[_TypedTable],
|
|
795
|
+
key: str,
|
|
796
|
+
parent_key: str = "",
|
|
770
797
|
) -> list[t.Any]:
|
|
771
798
|
"""Recursively build inner joins for a relationship and its nested relationships."""
|
|
772
799
|
db = self._get_db()
|
|
@@ -876,13 +903,21 @@ class QueryBuilder[T_MetaInstance: _TypedTable]:
|
|
|
876
903
|
# todo: add additional test, deduplicate
|
|
877
904
|
nested_key = f"{parent_key}.{nested_name}" if parent_key else f"{key}.{nested_name}"
|
|
878
905
|
select_args = self._process_relationship_for_left_join(
|
|
879
|
-
nested,
|
|
906
|
+
nested,
|
|
907
|
+
nested_name,
|
|
908
|
+
select_args,
|
|
909
|
+
left_joins,
|
|
910
|
+
other,
|
|
911
|
+
nested_key,
|
|
880
912
|
)
|
|
881
913
|
|
|
882
914
|
return select_args
|
|
883
915
|
|
|
884
916
|
def _ensure_relationship_fields(
|
|
885
|
-
self,
|
|
917
|
+
self,
|
|
918
|
+
select_args: list[t.Any],
|
|
919
|
+
other: t.Type[TypedTable],
|
|
920
|
+
select_fields: str,
|
|
886
921
|
) -> list[t.Any]:
|
|
887
922
|
"""Ensure required fields from relationship table are selected."""
|
|
888
923
|
if f"{other}." not in select_fields:
|
|
@@ -895,7 +930,10 @@ class QueryBuilder[T_MetaInstance: _TypedTable]:
|
|
|
895
930
|
return select_args
|
|
896
931
|
|
|
897
932
|
def _update_select_args_with_alias(
|
|
898
|
-
self,
|
|
933
|
+
self,
|
|
934
|
+
select_args: list[t.Any],
|
|
935
|
+
pre_alias: str,
|
|
936
|
+
other: t.Type[TypedTable],
|
|
899
937
|
) -> list[t.Any]:
|
|
900
938
|
"""Update select_args to use aliased table names."""
|
|
901
939
|
post_alias = str(other).split(" AS ")[-1]
|
|
@@ -71,7 +71,7 @@ def merge_permissions(*permission_sets: Permissions | None) -> Permissions:
|
|
|
71
71
|
|
|
72
72
|
for key in permission_types:
|
|
73
73
|
if key in permission_set:
|
|
74
|
-
merged[key] = merged[key] and bool(permission_set[key])
|
|
74
|
+
merged[key] = merged[key] and bool(permission_set[key]) # type: ignore
|
|
75
75
|
|
|
76
76
|
return t.cast(Permissions, merged)
|
|
77
77
|
|
|
@@ -188,6 +188,8 @@ class Reference(_Reference):
|
|
|
188
188
|
class Field(_Field):
|
|
189
189
|
"""Pydal Field object. Make mypy happy."""
|
|
190
190
|
|
|
191
|
+
_rname: str
|
|
192
|
+
|
|
191
193
|
|
|
192
194
|
class Rows(_Rows):
|
|
193
195
|
"""Pydal Rows object. Make mypy happy."""
|
|
@@ -283,7 +285,7 @@ class SelectKwargs(t.TypedDict, total=False):
|
|
|
283
285
|
groupby: "GroupBy | t.Iterable[GroupBy] | None"
|
|
284
286
|
having: "Having | None"
|
|
285
287
|
limitby: t.Optional[tuple[int, int]]
|
|
286
|
-
distinct: bool | Field | Expression | str
|
|
288
|
+
distinct: bool | Field | Expression | str | t.Iterable[str]
|
|
287
289
|
orderby_on_limitby: bool
|
|
288
290
|
cacheable: bool
|
|
289
291
|
cache: "CacheTuple"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import tempfile
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from testcontainers.postgres import PostgresContainer
|
|
5
|
+
|
|
6
|
+
from src.typedal import TypeDAL
|
|
7
|
+
|
|
8
|
+
postgres = PostgresContainer(
|
|
9
|
+
dbname="postgres",
|
|
10
|
+
username="someuser",
|
|
11
|
+
password="somepass",
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.fixture(scope="module", autouse=True)
|
|
16
|
+
def psql(request):
|
|
17
|
+
postgres.ports = {
|
|
18
|
+
5432: 9631, # as set in valid.env
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
request.addfinalizer(postgres.stop)
|
|
22
|
+
postgres.start()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.fixture
|
|
26
|
+
def dal_psql(psql):
|
|
27
|
+
conn_str = postgres.get_connection_url()
|
|
28
|
+
uri = "postgres://" + conn_str.split("://")[-1]
|
|
29
|
+
with tempfile.TemporaryDirectory() as d:
|
|
30
|
+
yield TypeDAL(uri, attempts=1, migrate=True, enable_typedal_caching=False, folder=d)
|
|
@@ -9,7 +9,6 @@ from pathlib import Path
|
|
|
9
9
|
|
|
10
10
|
import pytest
|
|
11
11
|
from contextlib_chdir import chdir
|
|
12
|
-
from testcontainers.postgres import PostgresContainer
|
|
13
12
|
|
|
14
13
|
from src.typedal import TypeDAL, TypedField, TypedTable
|
|
15
14
|
from src.typedal.config import (
|
|
@@ -20,22 +19,6 @@ from src.typedal.config import (
|
|
|
20
19
|
)
|
|
21
20
|
from src.typedal.fields import PointField, TimestampField, UUIDField
|
|
22
21
|
|
|
23
|
-
postgres = PostgresContainer(
|
|
24
|
-
dbname="postgres",
|
|
25
|
-
username="someuser",
|
|
26
|
-
password="somepass",
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
@pytest.fixture(scope="module", autouse=True)
|
|
31
|
-
def psql(request):
|
|
32
|
-
postgres.ports = {
|
|
33
|
-
5432: 9631, # as set in valid.env
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
request.addfinalizer(postgres.stop)
|
|
37
|
-
postgres.start()
|
|
38
|
-
|
|
39
22
|
|
|
40
23
|
@pytest.fixture
|
|
41
24
|
def at_temp_dir():
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import pytest
|
|
2
|
-
from pydal.objects import Query
|
|
2
|
+
from pydal.objects import Field, Query
|
|
3
3
|
|
|
4
4
|
from src.typedal import TypeDAL, TypedField, TypedTable, relationship
|
|
5
5
|
from typedal import QueryBuilder
|
|
6
|
+
from typedal.fields import rname
|
|
6
7
|
|
|
7
8
|
db = TypeDAL("sqlite:memory")
|
|
8
9
|
|
|
@@ -559,6 +560,48 @@ def test_orderby():
|
|
|
559
560
|
)
|
|
560
561
|
|
|
561
562
|
|
|
563
|
+
def test_select_kwargs_use_rname_psql(dal_psql: TypeDAL):
|
|
564
|
+
db = dal_psql
|
|
565
|
+
|
|
566
|
+
@db.define(rname="some_table")
|
|
567
|
+
class Sometable(TypedTable):
|
|
568
|
+
name = TypedField(str, rname="some_name")
|
|
569
|
+
|
|
570
|
+
Sometable.insert(name="B")
|
|
571
|
+
Sometable.insert(name="A")
|
|
572
|
+
Sometable.insert(name="A")
|
|
573
|
+
db.commit()
|
|
574
|
+
|
|
575
|
+
# default orderby/distinct
|
|
576
|
+
|
|
577
|
+
orderby_sql = Sometable.select(orderby=Sometable.name).to_sql().lower()
|
|
578
|
+
|
|
579
|
+
assert "sometable.name" not in orderby_sql
|
|
580
|
+
assert "some_table.some_name" in orderby_sql
|
|
581
|
+
|
|
582
|
+
distinct_sql1 = Sometable.select(Sometable.name, distinct=Sometable.name).to_sql().lower()
|
|
583
|
+
distinct_sql2 = Sometable.select(Sometable.name, distinct=(Sometable.name, Sometable.id)).to_sql().lower()
|
|
584
|
+
distinct_sql3 = Sometable.select(Sometable.name, distinct=("name", "id")).to_sql().lower()
|
|
585
|
+
|
|
586
|
+
for sql in (distinct_sql1, distinct_sql2, distinct_sql3):
|
|
587
|
+
assert "sometable.name" not in sql
|
|
588
|
+
assert "some_table.some_name" in sql
|
|
589
|
+
|
|
590
|
+
builder = Sometable.select(orderby=Sometable.name)
|
|
591
|
+
existing_orderby: Field = builder.select_kwargs.get("orderby")
|
|
592
|
+
|
|
593
|
+
existing_orderby_name = rname(existing_orderby)
|
|
594
|
+
sometable_name = rname(Sometable.name)
|
|
595
|
+
sometable_name_pydal = rname(db.sometable.name)
|
|
596
|
+
|
|
597
|
+
with pytest.raises(ValueError):
|
|
598
|
+
rname(TypedField(str, rname="some_name"))
|
|
599
|
+
|
|
600
|
+
for sql in (existing_orderby_name, sometable_name, sometable_name_pydal):
|
|
601
|
+
assert "sometable.name" not in sql
|
|
602
|
+
assert "some_table.some_name" in sql
|
|
603
|
+
|
|
604
|
+
|
|
562
605
|
def test_execute():
|
|
563
606
|
_setup_data()
|
|
564
607
|
|
|
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
|
|
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
|
|
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
|