TypeDAL 4.9.0__tar.gz → 4.9.1__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 (79) hide show
  1. {typedal-4.9.0 → typedal-4.9.1}/CHANGELOG.md +11 -0
  2. {typedal-4.9.0 → typedal-4.9.1}/PKG-INFO +1 -1
  3. {typedal-4.9.0 → typedal-4.9.1}/docs/2_defining_tables.md +21 -0
  4. {typedal-4.9.0 → typedal-4.9.1}/docs/3_building_queries.md +22 -0
  5. {typedal-4.9.0 → typedal-4.9.1}/src/typedal/__about__.py +1 -1
  6. {typedal-4.9.0 → typedal-4.9.1}/src/typedal/tables.py +3 -1
  7. {typedal-4.9.0 → typedal-4.9.1}/src/typedal/types.py +9 -2
  8. {typedal-4.9.0 → typedal-4.9.1}/tests/test_helpers.py +1 -0
  9. typedal-4.9.0/.crush/.gitignore +0 -1
  10. typedal-4.9.0/.crush/crush.db-shm +0 -0
  11. typedal-4.9.0/.crush/crush.db-wal +0 -0
  12. typedal-4.9.0/.crush/init +0 -0
  13. typedal-4.9.0/.crush/logs/crush.log +0 -37
  14. {typedal-4.9.0 → typedal-4.9.1}/.github/workflows/su6.yml +0 -0
  15. {typedal-4.9.0 → typedal-4.9.1}/.gitignore +0 -0
  16. {typedal-4.9.0 → typedal-4.9.1}/.readthedocs.yml +0 -0
  17. {typedal-4.9.0 → typedal-4.9.1}/README.md +0 -0
  18. {typedal-4.9.0 → typedal-4.9.1}/coverage.svg +0 -0
  19. {typedal-4.9.0 → typedal-4.9.1}/docs/10_advanced_apis.md +0 -0
  20. {typedal-4.9.0 → typedal-4.9.1}/docs/1_getting_started.md +0 -0
  21. {typedal-4.9.0 → typedal-4.9.1}/docs/4_relationships.md +0 -0
  22. {typedal-4.9.0 → typedal-4.9.1}/docs/5_py4web.md +0 -0
  23. {typedal-4.9.0 → typedal-4.9.1}/docs/6_migrations.md +0 -0
  24. {typedal-4.9.0 → typedal-4.9.1}/docs/7_configuration.md +0 -0
  25. {typedal-4.9.0 → typedal-4.9.1}/docs/8_mixins.md +0 -0
  26. {typedal-4.9.0 → typedal-4.9.1}/docs/9_memoization.md +0 -0
  27. {typedal-4.9.0 → typedal-4.9.1}/docs/css/code_blocks.css +0 -0
  28. {typedal-4.9.0 → typedal-4.9.1}/docs/index.md +0 -0
  29. {typedal-4.9.0 → typedal-4.9.1}/docs/requirements.txt +0 -0
  30. {typedal-4.9.0 → typedal-4.9.1}/example_new.py +0 -0
  31. {typedal-4.9.0 → typedal-4.9.1}/example_old.py +0 -0
  32. {typedal-4.9.0 → typedal-4.9.1}/mkdocs.yml +0 -0
  33. {typedal-4.9.0 → typedal-4.9.1}/pyproject.toml +0 -0
  34. {typedal-4.9.0 → typedal-4.9.1}/src/typedal/__init__.py +0 -0
  35. {typedal-4.9.0 → typedal-4.9.1}/src/typedal/caching.py +0 -0
  36. {typedal-4.9.0 → typedal-4.9.1}/src/typedal/cli.py +0 -0
  37. {typedal-4.9.0 → typedal-4.9.1}/src/typedal/config.py +0 -0
  38. {typedal-4.9.0 → typedal-4.9.1}/src/typedal/constants.py +0 -0
  39. {typedal-4.9.0 → typedal-4.9.1}/src/typedal/core.py +0 -0
  40. {typedal-4.9.0 → typedal-4.9.1}/src/typedal/define.py +0 -0
  41. {typedal-4.9.0 → typedal-4.9.1}/src/typedal/enum_helpers.py +0 -0
  42. {typedal-4.9.0 → typedal-4.9.1}/src/typedal/fields.py +0 -0
  43. {typedal-4.9.0 → typedal-4.9.1}/src/typedal/for_py4web.py +0 -0
  44. {typedal-4.9.0 → typedal-4.9.1}/src/typedal/for_web2py.py +0 -0
  45. {typedal-4.9.0 → typedal-4.9.1}/src/typedal/helpers.py +0 -0
  46. {typedal-4.9.0 → typedal-4.9.1}/src/typedal/mixins.py +0 -0
  47. {typedal-4.9.0 → typedal-4.9.1}/src/typedal/py.typed +0 -0
  48. {typedal-4.9.0 → typedal-4.9.1}/src/typedal/query_builder.py +0 -0
  49. {typedal-4.9.0 → typedal-4.9.1}/src/typedal/relationships.py +0 -0
  50. {typedal-4.9.0 → typedal-4.9.1}/src/typedal/rows.py +0 -0
  51. {typedal-4.9.0 → typedal-4.9.1}/src/typedal/serializers/as_json.py +0 -0
  52. {typedal-4.9.0 → typedal-4.9.1}/src/typedal/serializers/typescript.py +0 -0
  53. {typedal-4.9.0 → typedal-4.9.1}/src/typedal/web2py_py4web_shared.py +0 -0
  54. {typedal-4.9.0 → typedal-4.9.1}/tasks.py +0 -0
  55. {typedal-4.9.0 → typedal-4.9.1}/tests/__init__.py +0 -0
  56. {typedal-4.9.0 → typedal-4.9.1}/tests/configs/simple.toml +0 -0
  57. {typedal-4.9.0 → typedal-4.9.1}/tests/configs/valid.env +0 -0
  58. {typedal-4.9.0 → typedal-4.9.1}/tests/configs/valid.toml +0 -0
  59. {typedal-4.9.0 → typedal-4.9.1}/tests/py314_tests.py +0 -0
  60. {typedal-4.9.0 → typedal-4.9.1}/tests/test_cli.py +0 -0
  61. {typedal-4.9.0 → typedal-4.9.1}/tests/test_config.py +0 -0
  62. {typedal-4.9.0 → typedal-4.9.1}/tests/test_docs_examples.py +0 -0
  63. {typedal-4.9.0 → typedal-4.9.1}/tests/test_json.py +0 -0
  64. {typedal-4.9.0 → typedal-4.9.1}/tests/test_main.py +0 -0
  65. {typedal-4.9.0 → typedal-4.9.1}/tests/test_mixins.py +0 -0
  66. {typedal-4.9.0 → typedal-4.9.1}/tests/test_mypy.py +0 -0
  67. {typedal-4.9.0 → typedal-4.9.1}/tests/test_orm.py +0 -0
  68. {typedal-4.9.0 → typedal-4.9.1}/tests/test_py4web.py +0 -0
  69. {typedal-4.9.0 → typedal-4.9.1}/tests/test_query_builder.py +0 -0
  70. {typedal-4.9.0 → typedal-4.9.1}/tests/test_relationships.py +0 -0
  71. {typedal-4.9.0 → typedal-4.9.1}/tests/test_row.py +0 -0
  72. {typedal-4.9.0 → typedal-4.9.1}/tests/test_stats.py +0 -0
  73. {typedal-4.9.0 → typedal-4.9.1}/tests/test_table.py +0 -0
  74. {typedal-4.9.0 → typedal-4.9.1}/tests/test_typescript.py +0 -0
  75. {typedal-4.9.0 → typedal-4.9.1}/tests/test_typing_mypy.md +0 -0
  76. {typedal-4.9.0 → typedal-4.9.1}/tests/test_typing_pyright.md +0 -0
  77. {typedal-4.9.0 → typedal-4.9.1}/tests/test_web2py.py +0 -0
  78. {typedal-4.9.0 → typedal-4.9.1}/tests/test_xx_others.py +0 -0
  79. {typedal-4.9.0 → typedal-4.9.1}/tests/timings.py +0 -0
@@ -2,6 +2,17 @@
2
2
 
3
3
  <!--next-version-placeholder-->
4
4
 
5
+ ## v4.9.1 (2026-07-02)
6
+
7
+ ### Fix
8
+
9
+ * **typing:** `select(distinct=` can also be a str ([`dc05389`](https://github.com/trialandsuccess/TypeDAL/commit/dc05389e89102cfb5de4a3b174857b8a7866a005))
10
+ * Improved typing for Permissions ([`236e4a8`](https://github.com/trialandsuccess/TypeDAL/commit/236e4a8ebfc42ed438dc8f626c192c6baca52f02))
11
+
12
+ ### Documentation
13
+
14
+ * Added info about `permissions` in docs ([`a5a9a85`](https://github.com/trialandsuccess/TypeDAL/commit/a5a9a85bb737acf5d5521e8923df56b3608b1ef5))
15
+
5
16
  ## v4.9.0 (2026-06-12)
6
17
 
7
18
  ### Feature
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: TypeDAL
3
- Version: 4.9.0
3
+ Version: 4.9.1
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
@@ -89,6 +89,27 @@ Important constraints and behavior:
89
89
  | `Field('name', 'string', required=True)` | `name: str` | `name: TypedField[str]` | `name = TypedField(str, required=True)` | `name = StringField(required=True)` |
90
90
  | `Field('name', 'text', required=False)` | `name: typing.Optional[str]` or <br/> <code>name: str &#124; None</code> | `name: TypedField[typing.Optional[str]]` or <br/> <code>name: TypedField[str &#124; None]</code> | `name = TypedField(str, type="text", required=False)` | `name = StringField(required=False)` |
91
91
 
92
+ ### Table permissions
93
+
94
+ You can restrict table-level operations when defining a model:
95
+
96
+ ```python
97
+ @db.define(permissions={"read": True, "insert": False, "update": True, "delete": False})
98
+ class Article(TypedTable):
99
+ title: str
100
+ body: str
101
+ ```
102
+
103
+ Permissions are currently available for:
104
+
105
+ - `read`
106
+ - `insert`
107
+ - `update`
108
+ - `delete`
109
+
110
+ Any permission you do not specify is treated as allowed by default.
111
+ Use this when a table should exist in the schema but should not be writable, or when you want to prevent reads from a given model entirely.
112
+
92
113
  # Hooks
93
114
 
94
115
  Some logic can be added when data is added/edited/deleted from the database.
@@ -219,6 +219,28 @@ Be aware doing this might break some caching functionality!
219
219
 
220
220
  **Note:** For caching function results (instead of just query results), see [9. Function Memoization](./9_memoization.md).
221
221
 
222
+ ### permissions
223
+
224
+ TypeDAL lets you override permissions on a query builder without changing the underlying table definition.
225
+ This is useful when a query should be more restrictive or more permissive than the model defaults.
226
+
227
+ ```python
228
+ # deny reads for this query
229
+ restricted = Article.permissions(read=False).where(id=1)
230
+
231
+ # allow a write operation only for this chain
232
+ Article.where(id=1).permissions(update=True, delete=False).update(title="New title")
233
+ ```
234
+
235
+ The same method is available on `TypedTable` as a convenience wrapper:
236
+
237
+ ```python
238
+ Article.permissions(read=False).where(id=1).collect()
239
+ ```
240
+
241
+ Permissions merge across the table definition and the query chain, with the most restrictive value winning.
242
+ Supported flags are `read`, `insert`, `update`, and `delete`.
243
+
222
244
  ### Collecting
223
245
 
224
246
  The Query Builder has a few operations that don't return a new builder instance:
@@ -5,4 +5,4 @@ This file contains the Version info for this package.
5
5
  # SPDX-FileCopyrightText: 2023-present Robin van der Noord <robinvandernoord@gmail.com>
6
6
  #
7
7
  # SPDX-License-Identifier: MIT
8
- __version__ = "4.9.0"
8
+ __version__ = "4.9.1"
@@ -370,7 +370,9 @@ class TableMeta(type):
370
370
  """
371
371
  return QueryBuilder(self).cache(*deps, **kwargs)
372
372
 
373
- def permissions(self: t.Type[T_MetaInstance], **permissions: bool) -> "QueryBuilder[T_MetaInstance]":
373
+ def permissions(
374
+ self: t.Type[T_MetaInstance], **permissions: t.Unpack[Permissions]
375
+ ) -> "QueryBuilder[T_MetaInstance]":
374
376
  """
375
377
  See QueryBuilder.permissions!
376
378
  """
@@ -46,7 +46,14 @@ type AnyDict = dict[str, t.Any]
46
46
 
47
47
  PermissionType = t.Literal["read", "insert", "update", "delete"]
48
48
 
49
- type Permissions = dict[PermissionType, bool]
49
+
50
+ # type Permissions = dict[PermissionType, bool]
51
+ class Permissions(t.TypedDict):
52
+ # note: extra source of truth because the dynamic dict doesn't work for all type checkers
53
+ read: bool
54
+ insert: bool
55
+ update: bool
56
+ delete: bool
50
57
 
51
58
 
52
59
  def merge_permissions(*permission_sets: Permissions | None) -> Permissions:
@@ -276,7 +283,7 @@ class SelectKwargs(t.TypedDict, total=False):
276
283
  groupby: "GroupBy | t.Iterable[GroupBy] | None"
277
284
  having: "Having | None"
278
285
  limitby: t.Optional[tuple[int, int]]
279
- distinct: bool | Field | Expression
286
+ distinct: bool | Field | Expression | str
280
287
  orderby_on_limitby: bool
281
288
  cacheable: bool
282
289
  cache: "CacheTuple"
@@ -274,6 +274,7 @@ def test_sql_expression_314():
274
274
 
275
275
  test_sql_expression_314(database)
276
276
 
277
+
277
278
  def test_merge_permissions():
278
279
  combined = merge_permissions({"read": True, "insert": False}, {"read": False, "insert": False})
279
280
  assert len(combined) == 4
@@ -1 +0,0 @@
1
- *
Binary file
Binary file
typedal-4.9.0/.crush/init DELETED
File without changes
@@ -1,37 +0,0 @@
1
- {"time":"2026-01-20T14:19:21.359487106+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/config.(*catwalkSync).Get.func1","file":"github.com/charmbracelet/crush/internal/config/catwalk.go","line":55},"msg":"Fetching providers from Catwalk"}
2
- {"time":"2026-01-20T14:19:21.518115889+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/config.(*catwalkSync).Get.func1","file":"github.com/charmbracelet/crush/internal/config/catwalk.go","line":63},"msg":"Catwalk providers not modified"}
3
- {"time":"2026-01-20T14:19:21.52626179+01:00","level":"INFO","msg":"OK 20250424200609_initial.sql (1.52ms)"}
4
- {"time":"2026-01-20T14:19:21.526651475+01:00","level":"INFO","msg":"OK 20250515105448_add_summary_message_id.sql (330.71µs)"}
5
- {"time":"2026-01-20T14:19:21.526995425+01:00","level":"INFO","msg":"OK 20250624000000_add_created_at_indexes.sql (325.29µs)"}
6
- {"time":"2026-01-20T14:19:21.527303588+01:00","level":"INFO","msg":"OK 20250627000000_add_provider_to_messages.sql (293.15µs)"}
7
- {"time":"2026-01-20T14:19:21.52790648+01:00","level":"INFO","msg":"OK 20250810000000_add_is_summary_message.sql (495.71µs)"}
8
- {"time":"2026-01-20T14:19:21.528316924+01:00","level":"INFO","msg":"OK 20250812000000_add_todos_to_sessions.sql (389.38µs)"}
9
- {"time":"2026-01-20T14:19:21.528324939+01:00","level":"INFO","msg":"goose: successfully migrated database to version: 20250812000000"}
10
- {"time":"2026-01-20T14:19:21.528356959+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.(*App).initLSPClients","file":"github.com/charmbracelet/crush/internal/app/lsp.go","line":21},"msg":"LSP clients initialization started in background"}
11
- {"time":"2026-01-20T14:19:21.52843831+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.New.func1","file":"github.com/charmbracelet/crush/internal/app/app.go","line":102},"msg":"Initializing MCP clients"}
12
- {"time":"2026-01-20T14:21:06.413595152+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).generateTitle","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":788},"msg":"generated title with small model"}
13
- {"time":"2026-01-20T14:21:14.539289381+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).generateTitle","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":788},"msg":"generated title with small model"}
14
- {"time":"2026-01-20T14:21:15.710688424+01:00","level":"WARN","source":{"function":"github.com/charmbracelet/crush/internal/agent/tools.init.func1","file":"github.com/charmbracelet/crush/internal/agent/tools/rg.go","line":18},"msg":"Ripgrep (rg) not found in $PATH. Some grep features might be limited or slower."}
15
- {"time":"2026-01-20T14:21:17.08089755+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).generateTitle","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":788},"msg":"generated title with small model"}
16
- {"time":"2026-01-20T14:21:17.151106375+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).generateTitle","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":788},"msg":"generated title with small model"}
17
- {"time":"2026-01-20T14:22:05.503761296+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).generateTitle","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":788},"msg":"generated title with small model"}
18
- {"time":"2026-01-20T14:26:05.030425805+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).generateTitle","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":788},"msg":"generated title with small model"}
19
- {"time":"2026-01-20T14:36:26.778665189+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).generateTitle","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":788},"msg":"generated title with small model"}
20
- {"time":"2026-01-20T14:38:51.314473736+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).generateTitle","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":788},"msg":"generated title with small model"}
21
- {"time":"2026-01-20T15:02:22.602405814+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.(*App).Shutdown.func1","file":"github.com/charmbracelet/crush/internal/app/app.go","line":403},"msg":"Shutdown took 6.62949ms"}
22
- {"time":"2026-01-20T15:02:23.719585648+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/config.(*catwalkSync).Get.func1","file":"github.com/charmbracelet/crush/internal/config/catwalk.go","line":55},"msg":"Fetching providers from Catwalk"}
23
- {"time":"2026-01-20T15:02:23.881789126+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/config.(*catwalkSync).Get.func1","file":"github.com/charmbracelet/crush/internal/config/catwalk.go","line":63},"msg":"Catwalk providers not modified"}
24
- {"time":"2026-01-20T15:02:23.884044691+01:00","level":"INFO","msg":"goose: no migrations to run. current version: 20250812000000"}
25
- {"time":"2026-01-20T15:02:23.884082792+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.(*App).initLSPClients","file":"github.com/charmbracelet/crush/internal/app/lsp.go","line":21},"msg":"LSP clients initialization started in background"}
26
- {"time":"2026-01-20T15:02:23.884273487+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.New.func1","file":"github.com/charmbracelet/crush/internal/app/app.go","line":102},"msg":"Initializing MCP clients"}
27
- {"time":"2026-01-20T15:04:13.024457515+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).generateTitle","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":788},"msg":"generated title with small model"}
28
- {"time":"2026-01-20T15:04:14.232072885+01:00","level":"WARN","source":{"function":"github.com/charmbracelet/crush/internal/agent/tools.init.func1","file":"github.com/charmbracelet/crush/internal/agent/tools/rg.go","line":18},"msg":"Ripgrep (rg) not found in $PATH. Some grep features might be limited or slower."}
29
- {"time":"2026-01-20T15:11:24.612161916+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.(*App).Shutdown.func1","file":"github.com/charmbracelet/crush/internal/app/app.go","line":403},"msg":"Shutdown took 9.537141ms"}
30
- {"time":"2026-01-20T15:11:25.263960575+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/config.(*catwalkSync).Get.func1","file":"github.com/charmbracelet/crush/internal/config/catwalk.go","line":55},"msg":"Fetching providers from Catwalk"}
31
- {"time":"2026-01-20T15:11:25.431990051+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/config.(*catwalkSync).Get.func1","file":"github.com/charmbracelet/crush/internal/config/catwalk.go","line":63},"msg":"Catwalk providers not modified"}
32
- {"time":"2026-01-20T15:11:25.434282035+01:00","level":"INFO","msg":"goose: no migrations to run. current version: 20250812000000"}
33
- {"time":"2026-01-20T15:11:25.434366662+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.(*App).initLSPClients","file":"github.com/charmbracelet/crush/internal/app/lsp.go","line":21},"msg":"LSP clients initialization started in background"}
34
- {"time":"2026-01-20T15:11:25.434579879+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.New.func1","file":"github.com/charmbracelet/crush/internal/app/app.go","line":102},"msg":"Initializing MCP clients"}
35
- {"time":"2026-01-20T15:24:40.456010578+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).generateTitle","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":788},"msg":"generated title with small model"}
36
- {"time":"2026-01-20T15:24:51.673808433+01:00","level":"WARN","source":{"function":"github.com/charmbracelet/crush/internal/agent/tools.init.func1","file":"github.com/charmbracelet/crush/internal/agent/tools/rg.go","line":18},"msg":"Ripgrep (rg) not found in $PATH. Some grep features might be limited or slower."}
37
- {"time":"2026-01-20T15:26:03.868420566+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).Cancel","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":907},"msg":"Request cancellation initiated","session_id":"4c2d1684-8db2-4ffd-83bb-9c08fe850f29"}
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