interlaced 2.0.0a2__tar.gz → 2.0.0a3__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.
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/PKG-INFO +1 -1
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/pyproject.toml +1 -1
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/__init__.py +1 -1
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/config/config.py +49 -9
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlaced.egg-info/PKG-INFO +1 -1
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_warehouse_config.py +21 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/LICENSE +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/README.md +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/setup.cfg +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/checks/__init__.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/checks/builtin.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/checks/runner.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/checks/spec.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/cli/__init__.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/cli/main.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/config/__init__.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/contracts.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/dsl/__init__.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/dsl/decorators.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/dsl/discovery.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/dsl/sql_config.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/engines/__init__.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/engines/base.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/engines/duckdb.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/engines/postgres.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/engines/quack.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/engines/registry.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/exceptions.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/exports.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/graph/__init__.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/graph/column_lineage.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/graph/dag.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/graph/project.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/graph/selectors.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/ir/__init__.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/ir/canonicalize.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/ir/fingerprint.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/ir/relation.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/ir/schema.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/plan/__init__.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/plan/apply.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/plan/differ.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/plan/plan.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/plan/resolve.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/plan/run.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/project.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/py.typed +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/runtime/__init__.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/runtime/handles.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/runtime/python_model.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/scaffold.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/scheduler/__init__.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/scheduler/engine.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/scheduler/triggers.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/scheduler/worker.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/service/__init__.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/service/app.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/service/auth.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/state/__init__.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/state/interval.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/state/janitor.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/state/snapshot.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/state/store.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/strategies/__init__.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/strategies/base.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/strategies/full.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/strategies/full_merge.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/strategies/incremental_by_time.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/strategies/merge_by_key.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/strategies/scd_type_2.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/strategies/view.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/streaming/__init__.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/streaming/log.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/streaming/materializer.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlace/streaming/schema.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlaced.egg-info/SOURCES.txt +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlaced.egg-info/dependency_links.txt +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlaced.egg-info/entry_points.txt +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlaced.egg-info/requires.txt +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/src/interlaced.egg-info/top_level.txt +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_apply.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_checks.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_cli.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_cli_inspect.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_column_lineage.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_compile.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_contracts.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_dag.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_differ.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_discovery.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_dsl.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_duckdb_adapter.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_ephemeral.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_example.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_example_benchmark.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_example_platform_tour.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_exports.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_fingerprint.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_forward_only.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_incremental.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_incremental_python.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_init.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_interval.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_janitor.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_multi_engine.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_postgres_engine.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_python_models.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_rebuild_skip.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_run.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_scd2.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_scheduler.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_schema_evolution.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_selectors.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_service.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_sql_config.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_state_store.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_storage.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_strategies.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_streaming.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_table_sinks.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_transfers.py +0 -0
- {interlaced-2.0.0a2 → interlaced-2.0.0a3}/tests/test_worker_leases.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "interlaced"
|
|
7
|
-
version = "2.0.
|
|
7
|
+
version = "2.0.0a3"
|
|
8
8
|
description = "Python/SQL-first data platform: transformation, built-in orchestration, and durable streaming ingestion"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.12"
|
|
@@ -3,9 +3,12 @@
|
|
|
3
3
|
A small pydantic model with sensible defaults so a project works with no config
|
|
4
4
|
file at all. Paths are relative to the project root.
|
|
5
5
|
|
|
6
|
-
``${VAR}`` references anywhere in the YAML are substituted
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
``${VAR}`` references anywhere in the YAML are substituted before parsing, so
|
|
7
|
+
DSNs and secret values never need to be committed:
|
|
8
|
+
``database: "ducklake:postgres:${WAREHOUSE_DSN}"``. Values come from the real
|
|
9
|
+
environment first, then from a ``.env`` file next to the config (dotenv
|
|
10
|
+
KEY=VALUE lines; the process environment always wins). Unset variables are
|
|
11
|
+
left literal so a missing one surfaces as an obvious ``${VAR}`` in errors.
|
|
9
12
|
"""
|
|
10
13
|
|
|
11
14
|
from __future__ import annotations
|
|
@@ -22,6 +25,7 @@ from interlace.exceptions import ConfigurationError
|
|
|
22
25
|
CONFIG_FILE = "interlace.yaml"
|
|
23
26
|
|
|
24
27
|
_ENV_REF = re.compile(r"\$\{([A-Za-z_][A-Za-z0-9_]*)\}")
|
|
28
|
+
_ENV_KEY = re.compile(r"[A-Za-z_][A-Za-z0-9_]*")
|
|
25
29
|
|
|
26
30
|
|
|
27
31
|
class SecretConfig(BaseModel):
|
|
@@ -150,18 +154,54 @@ def _default_engine_from_top_level(config: ProjectConfig) -> EngineConfig:
|
|
|
150
154
|
)
|
|
151
155
|
|
|
152
156
|
|
|
153
|
-
def _interpolate_env(text: str) -> str:
|
|
154
|
-
"""Replace ``${VAR}``
|
|
155
|
-
missing variable surfaces as an obvious ``${VAR}``
|
|
156
|
-
|
|
157
|
+
def _interpolate_env(text: str, fallback: dict[str, str] | None = None) -> str:
|
|
158
|
+
"""Replace ``${VAR}`` from the process environment, then the ``.env`` fallback;
|
|
159
|
+
unset vars stay literal (so a missing variable surfaces as an obvious ``${VAR}``
|
|
160
|
+
in errors, never silently '')."""
|
|
161
|
+
extra = fallback or {}
|
|
162
|
+
|
|
163
|
+
def resolve(match: re.Match[str]) -> str:
|
|
164
|
+
name = match.group(1)
|
|
165
|
+
if name in os.environ:
|
|
166
|
+
return os.environ[name]
|
|
167
|
+
return extra.get(name, match.group(0))
|
|
168
|
+
|
|
169
|
+
return _ENV_REF.sub(resolve, text)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def load_dotenv(path: Path) -> dict[str, str]:
|
|
173
|
+
"""KEY=VALUE pairs from a ``.env`` file (missing file = empty). Supports the
|
|
174
|
+
common dotenv subset: comments and blank lines skipped, optional ``export``
|
|
175
|
+
prefix, optional single/double quotes around the value. Never mutates the
|
|
176
|
+
process environment — values only feed ``${VAR}`` interpolation."""
|
|
177
|
+
values: dict[str, str] = {}
|
|
178
|
+
if not path.exists():
|
|
179
|
+
return values
|
|
180
|
+
for line in path.read_text().splitlines():
|
|
181
|
+
stripped = line.strip()
|
|
182
|
+
if not stripped or stripped.startswith("#") or "=" not in stripped:
|
|
183
|
+
continue
|
|
184
|
+
key, _, raw = stripped.removeprefix("export ").partition("=")
|
|
185
|
+
key = key.strip()
|
|
186
|
+
if not _ENV_KEY.fullmatch(key):
|
|
187
|
+
continue
|
|
188
|
+
value = raw.strip()
|
|
189
|
+
if len(value) >= 2 and value[0] == value[-1] and value[0] in "\"'":
|
|
190
|
+
value = value[1:-1]
|
|
191
|
+
values[key] = value
|
|
192
|
+
return values
|
|
157
193
|
|
|
158
194
|
|
|
159
195
|
def load_config(path: Path) -> ProjectConfig:
|
|
160
|
-
"""Load and validate config, returning defaults when the file is absent.
|
|
196
|
+
"""Load and validate config, returning defaults when the file is absent.
|
|
197
|
+
|
|
198
|
+
``${VAR}`` references resolve from the process environment first, then from
|
|
199
|
+
a ``.env`` file sitting next to the config."""
|
|
161
200
|
if not path.exists():
|
|
162
201
|
return ProjectConfig()
|
|
202
|
+
dotenv = load_dotenv(path.parent / ".env")
|
|
163
203
|
try:
|
|
164
|
-
data = yaml.safe_load(_interpolate_env(path.read_text())) or {}
|
|
204
|
+
data = yaml.safe_load(_interpolate_env(path.read_text(), dotenv)) or {}
|
|
165
205
|
except yaml.YAMLError as exc:
|
|
166
206
|
raise ConfigurationError("could not parse config", details={"path": str(path), "error": str(exc)}) from exc
|
|
167
207
|
if not isinstance(data, dict):
|
|
@@ -236,3 +236,24 @@ def test_pg_guard_accepts_libpq_conventions(monkeypatch: pytest.MonkeyPatch) ->
|
|
|
236
236
|
_require_explicit_pg_host("service=warehouse", "t")
|
|
237
237
|
monkeypatch.setenv("PGHOST", "db.internal")
|
|
238
238
|
_require_explicit_pg_host("dbname=analytics", "t") # target named via libpq env
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def test_dotenv_feeds_interpolation(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
242
|
+
"""${VAR} resolves from .env next to the config; the process environment wins."""
|
|
243
|
+
monkeypatch.delenv("WH_PATH", raising=False)
|
|
244
|
+
(tmp_path / ".env").write_text('# warehouse\nexport WH_PATH="wh-from-dotenv.duckdb"\nIGNORED\n')
|
|
245
|
+
(tmp_path / "interlace.yaml").write_text("name: dotenv\ndatabase: ${WH_PATH}\n")
|
|
246
|
+
assert load_config(tmp_path / "interlace.yaml").database == "wh-from-dotenv.duckdb"
|
|
247
|
+
|
|
248
|
+
monkeypatch.setenv("WH_PATH", "wh-from-env.duckdb") # real environment beats .env
|
|
249
|
+
assert load_config(tmp_path / "interlace.yaml").database == "wh-from-env.duckdb"
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def test_dotenv_never_mutates_the_process_environment(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
253
|
+
import os
|
|
254
|
+
|
|
255
|
+
monkeypatch.delenv("DOTENV_ONLY", raising=False)
|
|
256
|
+
(tmp_path / ".env").write_text("DOTENV_ONLY=secret\n")
|
|
257
|
+
(tmp_path / "interlace.yaml").write_text("name: pure\ndatabase: ${DOTENV_ONLY}\n")
|
|
258
|
+
assert load_config(tmp_path / "interlace.yaml").database == "secret"
|
|
259
|
+
assert "DOTENV_ONLY" not in os.environ
|
|
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
|
|
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
|