hydraflow 0.7.0__tar.gz → 0.7.3__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {hydraflow-0.7.0 → hydraflow-0.7.3}/PKG-INFO +1 -1
- {hydraflow-0.7.0 → hydraflow-0.7.3}/pyproject.toml +2 -1
- {hydraflow-0.7.0 → hydraflow-0.7.3}/src/hydraflow/__init__.py +7 -5
- hydraflow-0.7.3/src/hydraflow/main.py +54 -0
- hydraflow-0.7.3/tests/main/base.py +27 -0
- hydraflow-0.7.3/tests/main/force_new_run.py +26 -0
- hydraflow-0.7.3/tests/main/restart.py +26 -0
- hydraflow-0.7.3/tests/main/skip.py +26 -0
- hydraflow-0.7.3/tests/main/test_base.py +58 -0
- hydraflow-0.7.3/tests/main/test_force_new_run.py +33 -0
- hydraflow-0.7.3/tests/main/test_restart.py +24 -0
- hydraflow-0.7.0/tests/context/test_preemption.py → hydraflow-0.7.3/tests/main/test_skip.py +22 -5
- hydraflow-0.7.3/tests/utils/__init__.py +0 -0
- hydraflow-0.7.0/tests/context/preemption.py +0 -43
- {hydraflow-0.7.0 → hydraflow-0.7.3}/.devcontainer/devcontainer.json +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/.devcontainer/postCreate.sh +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/.devcontainer/starship.toml +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/.gitattributes +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/.gitignore +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/LICENSE +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/README.md +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/apps/quickstart.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/mkdocs.yml +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/src/hydraflow/config.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/src/hydraflow/context.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/src/hydraflow/mlflow.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/src/hydraflow/param.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/src/hydraflow/py.typed +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/src/hydraflow/run_collection.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/src/hydraflow/run_data.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/src/hydraflow/run_info.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/src/hydraflow/utils.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/tests/__init__.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/tests/config/__init__.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/tests/config/overrides.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/tests/config/test_config.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/tests/config/test_overrides.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/tests/config/test_params.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/tests/conftest.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/tests/context/__init__.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/tests/context/chdir.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/tests/context/context.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/tests/context/logging.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/tests/context/rerun.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/tests/context/test_chdir.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/tests/context/test_context.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/tests/context/test_logging.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/tests/context/test_rerun.py +0 -0
- {hydraflow-0.7.0/tests/param → hydraflow-0.7.3/tests/main}/__init__.py +0 -0
- {hydraflow-0.7.0/tests/run → hydraflow-0.7.3/tests/param}/__init__.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/tests/param/params.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/tests/param/test_param.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/tests/param/test_params.py +0 -0
- {hydraflow-0.7.0/tests/utils → hydraflow-0.7.3/tests/run}/__init__.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/tests/run/filter.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/tests/run/run.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/tests/run/test_collection.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/tests/run/test_data.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/tests/run/test_filter.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/tests/run/test_info.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/tests/run/test_run.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/tests/test_mlflow.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/tests/utils/test_run.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/tests/utils/test_utils.py +0 -0
- {hydraflow-0.7.0 → hydraflow-0.7.3}/tests/utils/utils.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: hydraflow
|
3
|
-
Version: 0.7.
|
3
|
+
Version: 0.7.3
|
4
4
|
Summary: Hydraflow integrates Hydra and MLflow to manage and track machine learning experiments.
|
5
5
|
Project-URL: Documentation, https://daizutabi.github.io/hydraflow/
|
6
6
|
Project-URL: Source, https://github.com/daizutabi/hydraflow
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "hydraflow"
|
7
|
-
version = "0.7.
|
7
|
+
version = "0.7.3"
|
8
8
|
description = "Hydraflow integrates Hydra and MLflow to manage and track machine learning experiments."
|
9
9
|
readme = "README.md"
|
10
10
|
license = { file = "LICENSE" }
|
@@ -97,3 +97,4 @@ ignore = [
|
|
97
97
|
"SLF",
|
98
98
|
]
|
99
99
|
"apps/*.py" = ["D", "G", "INP"]
|
100
|
+
"src/hydraflow/main.py" = ["ANN201", "D401", "PLR0913"]
|
@@ -1,10 +1,11 @@
|
|
1
1
|
"""Integrate Hydra and MLflow to manage and track machine learning experiments."""
|
2
2
|
|
3
|
-
from .config import select_config, select_overrides
|
4
|
-
from .context import chdir_artifact, log_run, start_run
|
5
|
-
from .
|
6
|
-
from .
|
7
|
-
from .
|
3
|
+
from hydraflow.config import select_config, select_overrides
|
4
|
+
from hydraflow.context import chdir_artifact, log_run, start_run
|
5
|
+
from hydraflow.main import main
|
6
|
+
from hydraflow.mlflow import list_runs, search_runs, set_experiment
|
7
|
+
from hydraflow.run_collection import RunCollection
|
8
|
+
from hydraflow.utils import (
|
8
9
|
get_artifact_dir,
|
9
10
|
get_artifact_path,
|
10
11
|
get_hydra_output_dir,
|
@@ -25,6 +26,7 @@ __all__ = [
|
|
25
26
|
"load_config",
|
26
27
|
"load_overrides",
|
27
28
|
"log_run",
|
29
|
+
"main",
|
28
30
|
"remove_run",
|
29
31
|
"search_runs",
|
30
32
|
"select_config",
|
@@ -0,0 +1,54 @@
|
|
1
|
+
"""main decorator."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
from functools import wraps
|
6
|
+
from typing import TYPE_CHECKING, Any
|
7
|
+
|
8
|
+
import hydra
|
9
|
+
from hydra.core.config_store import ConfigStore
|
10
|
+
from mlflow.entities import RunStatus
|
11
|
+
|
12
|
+
import hydraflow
|
13
|
+
|
14
|
+
if TYPE_CHECKING:
|
15
|
+
from collections.abc import Callable
|
16
|
+
|
17
|
+
from mlflow.entities import Run
|
18
|
+
|
19
|
+
FINISHED = RunStatus.to_string(RunStatus.FINISHED)
|
20
|
+
|
21
|
+
|
22
|
+
def main(
|
23
|
+
node: Any,
|
24
|
+
config_name: str = "config",
|
25
|
+
*,
|
26
|
+
chdir: bool = False,
|
27
|
+
force_new_run: bool = False,
|
28
|
+
skip_finished: bool = True,
|
29
|
+
):
|
30
|
+
"""Main decorator."""
|
31
|
+
|
32
|
+
def decorator(app: Callable[[Run, Any], None]) -> Callable[[], None]:
|
33
|
+
ConfigStore.instance().store(name=config_name, node=node)
|
34
|
+
|
35
|
+
@wraps(app)
|
36
|
+
@hydra.main(version_base=None, config_name=config_name)
|
37
|
+
def inner_app(cfg: object) -> None:
|
38
|
+
hydraflow.set_experiment()
|
39
|
+
|
40
|
+
if force_new_run:
|
41
|
+
run = None
|
42
|
+
else:
|
43
|
+
rc = hydraflow.search_runs()
|
44
|
+
run = rc.try_get(cfg, override=True)
|
45
|
+
|
46
|
+
if skip_finished and run and run.info.status == FINISHED:
|
47
|
+
return
|
48
|
+
|
49
|
+
with hydraflow.start_run(cfg, run=run, chdir=chdir) as run:
|
50
|
+
app(run, cfg)
|
51
|
+
|
52
|
+
return inner_app
|
53
|
+
|
54
|
+
return decorator
|
@@ -0,0 +1,27 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from dataclasses import dataclass
|
4
|
+
from pathlib import Path
|
5
|
+
from typing import TYPE_CHECKING
|
6
|
+
|
7
|
+
import hydraflow
|
8
|
+
|
9
|
+
if TYPE_CHECKING:
|
10
|
+
from mlflow.entities import Run
|
11
|
+
|
12
|
+
|
13
|
+
@dataclass
|
14
|
+
class Config:
|
15
|
+
count: int = 0
|
16
|
+
|
17
|
+
|
18
|
+
@hydraflow.main(Config)
|
19
|
+
def app(run: Run, cfg: Config):
|
20
|
+
path = hydraflow.get_artifact_dir() / "a.txt"
|
21
|
+
path.write_text(f"{run.info.run_id},{cfg.count}")
|
22
|
+
path = hydraflow.get_artifact_dir() / "b.txt"
|
23
|
+
path.write_text(f"{Path.cwd().absolute().as_posix()}")
|
24
|
+
|
25
|
+
|
26
|
+
if __name__ == "__main__":
|
27
|
+
app()
|
@@ -0,0 +1,26 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from dataclasses import dataclass
|
4
|
+
from pathlib import Path
|
5
|
+
from typing import TYPE_CHECKING
|
6
|
+
|
7
|
+
import hydraflow
|
8
|
+
|
9
|
+
if TYPE_CHECKING:
|
10
|
+
from mlflow.entities import Run
|
11
|
+
|
12
|
+
|
13
|
+
@dataclass
|
14
|
+
class Config:
|
15
|
+
count: int = 0
|
16
|
+
|
17
|
+
|
18
|
+
@hydraflow.main(Config, chdir=True, force_new_run=True)
|
19
|
+
def app(run: Run, cfg: Config):
|
20
|
+
file = Path("a.txt")
|
21
|
+
text = file.read_text() if file.exists() else ""
|
22
|
+
file.write_text(text + f"{cfg.count}")
|
23
|
+
|
24
|
+
|
25
|
+
if __name__ == "__main__":
|
26
|
+
app()
|
@@ -0,0 +1,26 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from dataclasses import dataclass
|
4
|
+
from pathlib import Path
|
5
|
+
from typing import TYPE_CHECKING
|
6
|
+
|
7
|
+
import hydraflow
|
8
|
+
|
9
|
+
if TYPE_CHECKING:
|
10
|
+
from mlflow.entities import Run
|
11
|
+
|
12
|
+
|
13
|
+
@dataclass
|
14
|
+
class Config:
|
15
|
+
count: int = 0
|
16
|
+
|
17
|
+
|
18
|
+
@hydraflow.main(Config, chdir=True, skip_finished=False)
|
19
|
+
def app(run: Run, cfg: Config):
|
20
|
+
file = Path("a.txt")
|
21
|
+
text = file.read_text() if file.exists() else ""
|
22
|
+
file.write_text(text + f"{cfg.count}")
|
23
|
+
|
24
|
+
|
25
|
+
if __name__ == "__main__":
|
26
|
+
app()
|
@@ -0,0 +1,26 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from dataclasses import dataclass
|
4
|
+
from pathlib import Path
|
5
|
+
from typing import TYPE_CHECKING
|
6
|
+
|
7
|
+
import hydraflow
|
8
|
+
|
9
|
+
if TYPE_CHECKING:
|
10
|
+
from mlflow.entities import Run
|
11
|
+
|
12
|
+
|
13
|
+
@dataclass
|
14
|
+
class Config:
|
15
|
+
count: int = 0
|
16
|
+
|
17
|
+
|
18
|
+
@hydraflow.main(Config, chdir=True)
|
19
|
+
def app(run: Run, cfg: Config):
|
20
|
+
file = Path("a.txt")
|
21
|
+
text = file.read_text() if file.exists() else ""
|
22
|
+
file.write_text(text + f"{cfg.count} {run.info.run_id}\n")
|
23
|
+
|
24
|
+
|
25
|
+
if __name__ == "__main__":
|
26
|
+
app()
|
@@ -0,0 +1,58 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
|
3
|
+
import pytest
|
4
|
+
from mlflow.entities import Run
|
5
|
+
|
6
|
+
from hydraflow.run_collection import RunCollection
|
7
|
+
|
8
|
+
pytestmark = pytest.mark.xdist_group(name="group5")
|
9
|
+
|
10
|
+
|
11
|
+
@pytest.fixture(scope="module")
|
12
|
+
def rc(collect):
|
13
|
+
filename = "main/base.py"
|
14
|
+
args = ["-m", "count=1,2,3"]
|
15
|
+
|
16
|
+
return collect(filename, args)
|
17
|
+
|
18
|
+
|
19
|
+
def test_rc_len(rc: RunCollection):
|
20
|
+
assert len(rc) == 3
|
21
|
+
|
22
|
+
|
23
|
+
@pytest.fixture(scope="module", params=[1, 2, 3])
|
24
|
+
def run(rc: RunCollection, request: pytest.FixtureRequest):
|
25
|
+
return rc.get(count=request.param)
|
26
|
+
|
27
|
+
|
28
|
+
@pytest.fixture(scope="module")
|
29
|
+
def count(run: Run):
|
30
|
+
return int(run.data.params["count"])
|
31
|
+
|
32
|
+
|
33
|
+
@pytest.fixture(scope="module")
|
34
|
+
def text(run: Run):
|
35
|
+
from hydraflow.utils import get_artifact_path
|
36
|
+
|
37
|
+
path = get_artifact_path(run, "a.txt")
|
38
|
+
return path.read_text()
|
39
|
+
|
40
|
+
|
41
|
+
def test_run_id(run: Run, text: str):
|
42
|
+
assert text.split(",")[0] == run.info.run_id
|
43
|
+
|
44
|
+
|
45
|
+
def test_count(text: str, count: int):
|
46
|
+
assert text.split(",")[1] == str(count)
|
47
|
+
|
48
|
+
|
49
|
+
@pytest.fixture(scope="module")
|
50
|
+
def cwd(run: Run):
|
51
|
+
from hydraflow.utils import get_artifact_path
|
52
|
+
|
53
|
+
path = get_artifact_path(run, "b.txt")
|
54
|
+
return Path(path.read_text())
|
55
|
+
|
56
|
+
|
57
|
+
def test_cwd(cwd: Path, experiment_name: str):
|
58
|
+
assert cwd.name == experiment_name
|
@@ -0,0 +1,33 @@
|
|
1
|
+
import pytest
|
2
|
+
from mlflow.entities import Run
|
3
|
+
|
4
|
+
from hydraflow.run_collection import RunCollection
|
5
|
+
|
6
|
+
pytestmark = pytest.mark.xdist_group(name="group6")
|
7
|
+
|
8
|
+
|
9
|
+
@pytest.fixture(scope="module")
|
10
|
+
def rc(collect):
|
11
|
+
for _ in range(3):
|
12
|
+
rc = collect("main/force_new_run.py", ["count=3"])
|
13
|
+
return rc
|
14
|
+
|
15
|
+
|
16
|
+
def test_rc_len(rc: RunCollection):
|
17
|
+
assert len(rc) == 3
|
18
|
+
|
19
|
+
|
20
|
+
def test_rc_filter(rc: RunCollection):
|
21
|
+
assert len(rc.filter(count=3)) == 3
|
22
|
+
|
23
|
+
|
24
|
+
@pytest.fixture(scope="module", params=range(3))
|
25
|
+
def run(rc: RunCollection, request: pytest.FixtureRequest):
|
26
|
+
return rc[request.param]
|
27
|
+
|
28
|
+
|
29
|
+
def test_count(run: Run):
|
30
|
+
from hydraflow.utils import get_artifact_path
|
31
|
+
|
32
|
+
path = get_artifact_path(run, "a.txt")
|
33
|
+
assert path.read_text() == "3"
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import pytest
|
2
|
+
|
3
|
+
from hydraflow.run_collection import RunCollection
|
4
|
+
|
5
|
+
pytestmark = pytest.mark.xdist_group(name="group7")
|
6
|
+
|
7
|
+
|
8
|
+
@pytest.fixture(scope="module")
|
9
|
+
def rc(collect):
|
10
|
+
for _ in range(3):
|
11
|
+
rc = collect("main/restart.py", ["count=3"])
|
12
|
+
return rc
|
13
|
+
|
14
|
+
|
15
|
+
def test_rc_len(rc: RunCollection):
|
16
|
+
assert len(rc) == 1
|
17
|
+
|
18
|
+
|
19
|
+
def test_count(rc: RunCollection):
|
20
|
+
from hydraflow.utils import get_artifact_path
|
21
|
+
|
22
|
+
run = rc.get(count=3)
|
23
|
+
path = get_artifact_path(run, "a.txt")
|
24
|
+
assert path.read_text() == "333"
|
@@ -12,7 +12,7 @@ def rc(collect):
|
|
12
12
|
client = MlflowClient()
|
13
13
|
running = RunStatus.to_string(RunStatus.RUNNING)
|
14
14
|
|
15
|
-
filename = "
|
15
|
+
filename = "main/skip.py"
|
16
16
|
args = ["-m", "count=1,2,3"]
|
17
17
|
|
18
18
|
rc = collect(filename, args)
|
@@ -32,10 +32,27 @@ def run(rc: RunCollection, request: pytest.FixtureRequest):
|
|
32
32
|
return rc.get(count=request.param)
|
33
33
|
|
34
34
|
|
35
|
-
|
35
|
+
@pytest.fixture(scope="module")
|
36
|
+
def count(run: Run):
|
37
|
+
return int(run.data.params["count"])
|
38
|
+
|
39
|
+
|
40
|
+
@pytest.fixture(scope="module")
|
41
|
+
def text(run: Run):
|
36
42
|
from hydraflow.utils import get_artifact_path
|
37
43
|
|
38
|
-
count = int(run.data.params["count"])
|
39
44
|
path = get_artifact_path(run, "a.txt")
|
40
|
-
|
41
|
-
|
45
|
+
return path.read_text()
|
46
|
+
|
47
|
+
|
48
|
+
def test_count(text: str, count: int):
|
49
|
+
assert len(text.splitlines()) == count
|
50
|
+
|
51
|
+
|
52
|
+
def test_config(text: str, count: int):
|
53
|
+
assert int(text.split(" ", maxsplit=1)[0]) == count
|
54
|
+
|
55
|
+
|
56
|
+
def test_run(text: str, run: Run):
|
57
|
+
line = text.splitlines()[-1]
|
58
|
+
assert line.split(" ", maxsplit=1)[1] == run.info.run_id
|
File without changes
|
@@ -1,43 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
from dataclasses import dataclass
|
4
|
-
from typing import TYPE_CHECKING
|
5
|
-
|
6
|
-
import hydra
|
7
|
-
from hydra.core.config_store import ConfigStore
|
8
|
-
|
9
|
-
import hydraflow
|
10
|
-
|
11
|
-
if TYPE_CHECKING:
|
12
|
-
from pathlib import Path
|
13
|
-
|
14
|
-
|
15
|
-
@dataclass
|
16
|
-
class Config:
|
17
|
-
count: int = 0
|
18
|
-
|
19
|
-
|
20
|
-
ConfigStore.instance().store(name="config", node=Config)
|
21
|
-
|
22
|
-
|
23
|
-
@hydra.main(version_base=None, config_name="config")
|
24
|
-
def app(cfg: Config):
|
25
|
-
hydraflow.set_experiment()
|
26
|
-
|
27
|
-
rc = hydraflow.list_runs().filter(cfg, override=True)
|
28
|
-
|
29
|
-
if rc.filter(status="finished"):
|
30
|
-
return
|
31
|
-
|
32
|
-
with hydraflow.start_run(cfg, run=rc.try_one()) as run:
|
33
|
-
log(hydraflow.get_artifact_dir(run))
|
34
|
-
|
35
|
-
|
36
|
-
def log(path: Path):
|
37
|
-
file = path / "a.txt"
|
38
|
-
text = file.read_text() if file.exists() else ""
|
39
|
-
file.write_text(text + "a")
|
40
|
-
|
41
|
-
|
42
|
-
if __name__ == "__main__":
|
43
|
-
app()
|
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
|