lamin_cli 0.21.4__py2.py3-none-any.whl → 1.0a1__py2.py3-none-any.whl
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.
- lamin_cli/__init__.py +1 -1
- lamin_cli/__main__.py +2 -2
- lamin_cli/_load.py +61 -35
- lamin_cli/_save.py +32 -54
- {lamin_cli-0.21.4.dist-info → lamin_cli-1.0a1.dist-info}/METADATA +2 -2
- lamin_cli-1.0a1.dist-info/RECORD +12 -0
- {lamin_cli-0.21.4.dist-info → lamin_cli-1.0a1.dist-info}/WHEEL +1 -1
- lamin_cli-0.21.4.dist-info/RECORD +0 -12
- {lamin_cli-0.21.4.dist-info → lamin_cli-1.0a1.dist-info}/LICENSE +0 -0
- {lamin_cli-0.21.4.dist-info → lamin_cli-1.0a1.dist-info}/entry_points.txt +0 -0
lamin_cli/__init__.py
CHANGED
lamin_cli/__main__.py
CHANGED
|
@@ -165,7 +165,7 @@ def connect(instance: str):
|
|
|
165
165
|
from lamindb_setup import settings as settings_, connect as connect_
|
|
166
166
|
|
|
167
167
|
settings_.auto_connect = True
|
|
168
|
-
return connect_(slug=instance)
|
|
168
|
+
return connect_(slug=instance, _reload_lamindb=False)
|
|
169
169
|
|
|
170
170
|
|
|
171
171
|
@main.command()
|
|
@@ -239,7 +239,7 @@ def load(entity: str, uid: str = None, key: str = None, with_env: bool = False):
|
|
|
239
239
|
# f"! please use: lamin connect {entity}"
|
|
240
240
|
# )
|
|
241
241
|
settings_.auto_connect = True
|
|
242
|
-
return connect(slug=entity)
|
|
242
|
+
return connect(slug=entity, _reload_lamindb=False)
|
|
243
243
|
else:
|
|
244
244
|
from lamin_cli._load import load as load_
|
|
245
245
|
|
lamin_cli/_load.py
CHANGED
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
from typing import Tuple
|
|
3
3
|
from lamin_utils import logger
|
|
4
4
|
import shutil
|
|
5
|
+
import re
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
|
|
7
8
|
|
|
@@ -27,7 +28,7 @@ def load(entity: str, uid: str = None, key: str = None, with_env: bool = False):
|
|
|
27
28
|
instance = ln_setup.settings.instance.slug
|
|
28
29
|
|
|
29
30
|
ln_setup.connect(instance)
|
|
30
|
-
|
|
31
|
+
import lamindb as ln
|
|
31
32
|
|
|
32
33
|
def script_to_notebook(
|
|
33
34
|
transform: ln.Transform, notebook_path: Path, bump_revision: bool = False
|
|
@@ -35,16 +36,48 @@ def load(entity: str, uid: str = None, key: str = None, with_env: bool = False):
|
|
|
35
36
|
import jupytext
|
|
36
37
|
from lamin_utils._base62 import increment_base62
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
if notebook_path.suffix == ".ipynb":
|
|
40
|
+
# below is backward compat
|
|
41
|
+
if "# # transform.name" in transform.source_code:
|
|
42
|
+
new_content = transform.source_code.replace(
|
|
43
|
+
"# # transform.name", f"# # {transform.description}"
|
|
44
|
+
)
|
|
45
|
+
elif transform.source_code.startswith("# %% [markdown]\n#\n"):
|
|
46
|
+
new_content = transform.source_code.replace(
|
|
47
|
+
"# %% [markdown]\n#\n",
|
|
48
|
+
f"# %% [markdown]\n# # {transform.description}\n",
|
|
49
|
+
)
|
|
50
|
+
else: # R notebook
|
|
51
|
+
# Pattern to match title only within YAML header section
|
|
52
|
+
title_pattern = r'^---\n.*?title:\s*"([^"]*)".*?---'
|
|
53
|
+
title_match = re.search(
|
|
54
|
+
title_pattern, transform.source_code, flags=re.DOTALL | re.MULTILINE
|
|
55
|
+
)
|
|
56
|
+
new_content = transform.source_code
|
|
57
|
+
if title_match:
|
|
58
|
+
current_title = title_match.group(1)
|
|
59
|
+
if current_title != transform.description:
|
|
60
|
+
pattern = r'^(---\n.*?title:\s*)"([^"]*)"(.*?---)'
|
|
61
|
+
replacement = f'\\1"{transform.description}"\\3'
|
|
62
|
+
new_content = re.sub(
|
|
63
|
+
pattern,
|
|
64
|
+
replacement,
|
|
65
|
+
new_content,
|
|
66
|
+
flags=re.DOTALL | re.MULTILINE,
|
|
67
|
+
)
|
|
68
|
+
logger.important(
|
|
69
|
+
f"fixed title: {current_title} → {transform.description}"
|
|
70
|
+
)
|
|
41
71
|
if bump_revision:
|
|
42
72
|
uid = transform.uid
|
|
43
73
|
new_uid = f"{uid[:-4]}{increment_base62(uid[-4:])}"
|
|
44
|
-
|
|
74
|
+
new_content = new_content.replace(uid, new_uid)
|
|
45
75
|
logger.important(f"updated uid: {uid} → {new_uid}")
|
|
46
|
-
|
|
47
|
-
|
|
76
|
+
if notebook_path.suffix == ".ipynb":
|
|
77
|
+
notebook = jupytext.reads(new_content, fmt="py:percent")
|
|
78
|
+
jupytext.write(notebook, notebook_path)
|
|
79
|
+
else:
|
|
80
|
+
notebook_path.write_text(new_content)
|
|
48
81
|
|
|
49
82
|
query_by_uid = uid is not None
|
|
50
83
|
|
|
@@ -61,42 +94,36 @@ def load(entity: str, uid: str = None, key: str = None, with_env: bool = False):
|
|
|
61
94
|
transforms = ln.Transform.objects.filter(key=key, source_code__isnull=False)
|
|
62
95
|
|
|
63
96
|
if (n_transforms := len(transforms)) == 0:
|
|
64
|
-
err_msg =
|
|
65
|
-
f"uid strating with {uid}"
|
|
66
|
-
if query_by_uid
|
|
67
|
-
else f"key={key} and source_code"
|
|
68
|
-
)
|
|
97
|
+
err_msg = f"uid {uid}" if query_by_uid else f"key={key} and source_code"
|
|
69
98
|
raise SystemExit(f"Transform with {err_msg} does not exist.")
|
|
70
99
|
|
|
71
100
|
if n_transforms > 1:
|
|
72
101
|
transforms = transforms.order_by("-created_at")
|
|
73
102
|
transform = transforms.first()
|
|
74
103
|
|
|
75
|
-
|
|
76
|
-
if
|
|
77
|
-
|
|
104
|
+
target_relpath = Path(transform.key)
|
|
105
|
+
if len(target_relpath.parents) > 1:
|
|
106
|
+
logger.important(
|
|
107
|
+
"preserve the folder structure for versioning:"
|
|
108
|
+
f" {target_relpath.parent}/"
|
|
109
|
+
)
|
|
110
|
+
target_relpath.parent.mkdir(parents=True, exist_ok=True)
|
|
111
|
+
if target_relpath.exists():
|
|
112
|
+
response = input(f"! {target_relpath} exists: replace? (y/n)")
|
|
78
113
|
if response != "y":
|
|
79
114
|
raise SystemExit("Aborted.")
|
|
80
|
-
if transform._source_code_artifact_id is not None: # backward compat
|
|
81
|
-
# need lamindb here to have .cache() available
|
|
82
|
-
import lamindb as ln
|
|
83
115
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
target_filename += transform._source_code_artifact.suffix
|
|
88
|
-
shutil.move(filepath_cache, target_filename)
|
|
89
|
-
elif transform.source_code is not None:
|
|
90
|
-
if transform.key.endswith(".ipynb"):
|
|
91
|
-
script_to_notebook(transform, target_filename, bump_revision=True)
|
|
116
|
+
if transform.source_code is not None:
|
|
117
|
+
if target_relpath.suffix in (".ipynb", ".Rmd", ".qmd"):
|
|
118
|
+
script_to_notebook(transform, target_relpath, bump_revision=True)
|
|
92
119
|
else:
|
|
93
|
-
|
|
120
|
+
target_relpath.write_text(transform.source_code)
|
|
94
121
|
else:
|
|
95
122
|
raise SystemExit("No source code available for this transform.")
|
|
96
|
-
logger.important(f"{transform.type} is here: {target_filename}")
|
|
97
|
-
if with_env:
|
|
98
|
-
import lamindb as ln
|
|
99
123
|
|
|
124
|
+
logger.important(f"{transform.type} is here: {target_relpath}")
|
|
125
|
+
|
|
126
|
+
if with_env:
|
|
100
127
|
ln.settings.track_run_inputs = False
|
|
101
128
|
if (
|
|
102
129
|
transform.latest_run is not None
|
|
@@ -104,16 +131,15 @@ def load(entity: str, uid: str = None, key: str = None, with_env: bool = False):
|
|
|
104
131
|
):
|
|
105
132
|
filepath_env_cache = transform.latest_run.environment.cache()
|
|
106
133
|
target_env_filename = (
|
|
107
|
-
|
|
134
|
+
target_relpath.parent / f"{target_relpath.stem}__requirements.txt"
|
|
108
135
|
)
|
|
109
136
|
shutil.move(filepath_env_cache, target_env_filename)
|
|
110
137
|
logger.important(f"environment is here: {target_env_filename}")
|
|
111
138
|
else:
|
|
112
139
|
logger.warning("latest transform run with environment doesn't exist")
|
|
113
|
-
return target_filename
|
|
114
|
-
elif entity == "artifact":
|
|
115
|
-
import lamindb as ln
|
|
116
140
|
|
|
141
|
+
return target_relpath
|
|
142
|
+
elif entity == "artifact":
|
|
117
143
|
ln.settings.track_run_inputs = False
|
|
118
144
|
|
|
119
145
|
if query_by_uid:
|
|
@@ -124,7 +150,7 @@ def load(entity: str, uid: str = None, key: str = None, with_env: bool = False):
|
|
|
124
150
|
artifacts = ln.Artifact.filter(key=key)
|
|
125
151
|
|
|
126
152
|
if (n_artifacts := len(artifacts)) == 0:
|
|
127
|
-
err_msg = f"uid
|
|
153
|
+
err_msg = f"uid={uid}" if query_by_uid else f"key={key}"
|
|
128
154
|
raise SystemExit(f"Artifact with {err_msg} does not exist.")
|
|
129
155
|
|
|
130
156
|
if n_artifacts > 1:
|
lamin_cli/_save.py
CHANGED
|
@@ -1,43 +1,27 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
from typing import Union
|
|
4
|
-
import lamindb_setup as ln_setup
|
|
5
4
|
from lamin_utils import logger
|
|
6
5
|
import re
|
|
7
6
|
|
|
8
7
|
|
|
9
|
-
def parse_uid_from_code(
|
|
10
|
-
content: str, suffix: str
|
|
11
|
-
) -> tuple[str | None, str | None, str | None]:
|
|
8
|
+
def parse_uid_from_code(content: str, suffix: str) -> str | None:
|
|
12
9
|
if suffix == ".py":
|
|
13
10
|
track_pattern = re.compile(
|
|
14
11
|
r'ln\.track\(\s*(?:transform\s*=\s*)?(["\'])([a-zA-Z0-9]{16})\1'
|
|
15
12
|
)
|
|
16
|
-
# backward compat
|
|
17
13
|
uid_pattern = re.compile(r'\.context\.uid\s*=\s*["\']([^"\']+)["\']')
|
|
18
|
-
stem_uid_pattern = re.compile(
|
|
19
|
-
r'\.transform\.stem_uid\s*=\s*["\']([^"\']+)["\']'
|
|
20
|
-
)
|
|
21
|
-
version_pattern = re.compile(r'\.transform\.version\s*=\s*["\']([^"\']+)["\']')
|
|
22
14
|
elif suffix == ".ipynb":
|
|
23
15
|
track_pattern = re.compile(
|
|
24
16
|
r'ln\.track\(\s*(?:transform\s*=\s*)?(?:\\"|\')([a-zA-Z0-9]{16})(?:\\"|\')'
|
|
25
17
|
)
|
|
26
18
|
# backward compat
|
|
27
19
|
uid_pattern = re.compile(r'\.context\.uid\s*=\s*\\["\']([^"\']+)\\["\']')
|
|
28
|
-
stem_uid_pattern = re.compile(
|
|
29
|
-
r'\.transform\.stem_uid\s*=\s*\\["\']([^"\']+)\\["\']'
|
|
30
|
-
)
|
|
31
|
-
version_pattern = re.compile(
|
|
32
|
-
r'\.transform\.version\s*=\s*\\["\']([^"\']+)\\["\']'
|
|
33
|
-
)
|
|
34
20
|
elif suffix in {".R", ".qmd", ".Rmd"}:
|
|
35
21
|
track_pattern = re.compile(
|
|
36
22
|
r'track\(\s*(?:transform\s*=\s*)?([\'"])([a-zA-Z0-9]{16})\1'
|
|
37
23
|
)
|
|
38
24
|
uid_pattern = None
|
|
39
|
-
stem_uid_pattern = None
|
|
40
|
-
version_pattern = None
|
|
41
25
|
else:
|
|
42
26
|
raise SystemExit(
|
|
43
27
|
"Only .py, .ipynb, .R, .qmd, .Rmd files are supported for saving"
|
|
@@ -48,26 +32,12 @@ def parse_uid_from_code(
|
|
|
48
32
|
uid_match = track_pattern.search(content)
|
|
49
33
|
group_index = 1 if suffix == ".ipynb" else 2
|
|
50
34
|
uid = uid_match.group(group_index) if uid_match else None
|
|
51
|
-
stem_uid = None
|
|
52
|
-
version = None
|
|
53
35
|
|
|
54
36
|
if uid_pattern is not None and uid is None:
|
|
55
37
|
uid_match = uid_pattern.search(content)
|
|
56
38
|
uid = uid_match.group(1) if uid_match else None
|
|
57
|
-
if stem_uid_pattern is not None:
|
|
58
|
-
stem_uid_match = stem_uid_pattern.search(content)
|
|
59
|
-
stem_uid = stem_uid_match.group(1) if stem_uid_match else None
|
|
60
|
-
if version_pattern is not None:
|
|
61
|
-
version_match = version_pattern.search(content)
|
|
62
|
-
version = version_match.group(1) if version_match else None
|
|
63
|
-
|
|
64
|
-
if uid is None and (stem_uid is None or version is None):
|
|
65
|
-
target = "script" if suffix in {".py", ".R"} else "notebook"
|
|
66
|
-
raise SystemExit(
|
|
67
|
-
f"Cannot infer transform uid. Did you run `ln.track()` in your {target}?"
|
|
68
|
-
)
|
|
69
39
|
|
|
70
|
-
return uid
|
|
40
|
+
return uid
|
|
71
41
|
|
|
72
42
|
|
|
73
43
|
def save_from_filepath_cli(
|
|
@@ -76,6 +46,8 @@ def save_from_filepath_cli(
|
|
|
76
46
|
description: str | None,
|
|
77
47
|
registry: str | None,
|
|
78
48
|
) -> str | None:
|
|
49
|
+
import lamindb_setup as ln_setup
|
|
50
|
+
|
|
79
51
|
if not isinstance(filepath, Path):
|
|
80
52
|
filepath = Path(filepath)
|
|
81
53
|
|
|
@@ -94,14 +66,23 @@ def save_from_filepath_cli(
|
|
|
94
66
|
"R": set([".R", ".qmd", ".Rmd"]),
|
|
95
67
|
}
|
|
96
68
|
|
|
97
|
-
if
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
69
|
+
if filepath.suffix in {".qmd", ".Rmd"}:
|
|
70
|
+
if not (
|
|
71
|
+
filepath.with_suffix(".html").exists()
|
|
72
|
+
or filepath.with_suffix(".nb.html").exists()
|
|
73
|
+
):
|
|
74
|
+
raise SystemExit(
|
|
75
|
+
f"Please export your {filepath.suffix} file as an html file here"
|
|
76
|
+
f" {filepath.with_suffix('.html')}"
|
|
77
|
+
)
|
|
78
|
+
if (
|
|
79
|
+
filepath.with_suffix(".html").exists()
|
|
80
|
+
and filepath.with_suffix(".nb.html").exists()
|
|
81
|
+
):
|
|
82
|
+
raise SystemExit(
|
|
83
|
+
f'Please delete one of\n - {filepath.with_suffix(".html")}\n -'
|
|
84
|
+
f' {filepath.with_suffix(".nb.html")}'
|
|
85
|
+
)
|
|
105
86
|
|
|
106
87
|
if registry is None:
|
|
107
88
|
registry = (
|
|
@@ -128,9 +109,9 @@ def save_from_filepath_cli(
|
|
|
128
109
|
elif registry == "transform":
|
|
129
110
|
with open(filepath) as file:
|
|
130
111
|
content = file.read()
|
|
131
|
-
uid
|
|
132
|
-
logger.important(f"mapped '{filepath}' on uid '{uid}'")
|
|
112
|
+
uid = parse_uid_from_code(content, filepath.suffix)
|
|
133
113
|
if uid is not None:
|
|
114
|
+
logger.important(f"mapped '{filepath}' on uid '{uid}'")
|
|
134
115
|
transform = ln.Transform.filter(uid=uid).one_or_none()
|
|
135
116
|
if transform is None:
|
|
136
117
|
logger.error(
|
|
@@ -139,10 +120,17 @@ def save_from_filepath_cli(
|
|
|
139
120
|
)
|
|
140
121
|
return "not-tracked-in-transform-registry"
|
|
141
122
|
else:
|
|
142
|
-
transform = ln.Transform.
|
|
123
|
+
transform = ln.Transform.filter(key=filepath.name).one_or_none()
|
|
124
|
+
if transform is None:
|
|
125
|
+
transform = ln.Transform(
|
|
126
|
+
name=filepath.name,
|
|
127
|
+
key=filepath.name,
|
|
128
|
+
type="script" if filepath.suffix in {".R", ".py"} else "notebook",
|
|
129
|
+
).save()
|
|
130
|
+
logger.important(f"created Transform('{transform.uid}')")
|
|
143
131
|
# latest run of this transform by user
|
|
144
132
|
run = ln.Run.filter(transform=transform).order_by("-started_at").first()
|
|
145
|
-
if run.created_by.id != ln_setup.settings.user.id:
|
|
133
|
+
if run is not None and run.created_by.id != ln_setup.settings.user.id:
|
|
146
134
|
response = input(
|
|
147
135
|
"You are trying to save a transform created by another user: Source"
|
|
148
136
|
" and report files will be tagged with *your* user id. Proceed?"
|
|
@@ -156,16 +144,6 @@ def save_from_filepath_cli(
|
|
|
156
144
|
filepath=filepath,
|
|
157
145
|
from_cli=True,
|
|
158
146
|
)
|
|
159
|
-
if filepath.suffix in {".qmd", ".Rmd"}:
|
|
160
|
-
report_file = ln.Artifact(
|
|
161
|
-
filepath.with_suffix(".html"), # validated at the top that this exists
|
|
162
|
-
description=f"Report of run {run.uid}",
|
|
163
|
-
visibility=0, # hidden file
|
|
164
|
-
run=False,
|
|
165
|
-
)
|
|
166
|
-
report_file.save(upload=True, print_progress=False)
|
|
167
|
-
run.report = report_file
|
|
168
|
-
run.save()
|
|
169
147
|
return return_code
|
|
170
148
|
else:
|
|
171
149
|
raise SystemExit("Allowed values for '--registry' are: 'artifact', 'transform'")
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
lamin_cli/__init__.py,sha256=ygApcHBvEoSGZBbdy7cdCV_MnVUaYbGis_FDxgtNxm4,40
|
|
2
|
+
lamin_cli/__main__.py,sha256=CnwhcWeSJwLdq4esnewNKc6Z-T4AuZ8EMDmILfpBZF0,9946
|
|
3
|
+
lamin_cli/_cache.py,sha256=kW8rqlMwQeOngm9uq2gjzPVl3EBrwh6W2F2AvyBFABY,799
|
|
4
|
+
lamin_cli/_load.py,sha256=I2UGifZdIbbLm0JBOsuoR2CZeq5_uQR1BwIujDq8WfA,6694
|
|
5
|
+
lamin_cli/_migration.py,sha256=xTbad6aDUwuK0QvWCmDW3f8-xaqwisS-Cqf-LbD1WRI,1071
|
|
6
|
+
lamin_cli/_save.py,sha256=PrvZSL02jU1BWLUEVTTQsn2MLi942RKOyN3Qd3qfl6s,5677
|
|
7
|
+
lamin_cli/_settings.py,sha256=iS37mcQUHKRWxi2sHnAojEI6sWk3w232qwG-GeY2_Qc,1141
|
|
8
|
+
lamin_cli-1.0a1.dist-info/entry_points.txt,sha256=Qms85i9cZPlu-U7RnVZhFsF7vJ9gaLZUFkCjcGcXTpg,49
|
|
9
|
+
lamin_cli-1.0a1.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
10
|
+
lamin_cli-1.0a1.dist-info/WHEEL,sha256=ssQ84EZ5gH1pCOujd3iW7HClo_O_aDaClUbX4B8bjKY,100
|
|
11
|
+
lamin_cli-1.0a1.dist-info/METADATA,sha256=4t0BVXkfQyWzahzefrlFl2vG0MRyYmxfNoT6rvQdcJg,337
|
|
12
|
+
lamin_cli-1.0a1.dist-info/RECORD,,
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
lamin_cli/__init__.py,sha256=TdeUIkMA4v5UQztsiYP7y--juAZplFXt-MOJAiS-1ao,41
|
|
2
|
-
lamin_cli/__main__.py,sha256=-QAfgrYKcPjl0qkiWr8Tfm5wRaaZ99rW2atjIBnBn9Y,9900
|
|
3
|
-
lamin_cli/_cache.py,sha256=kW8rqlMwQeOngm9uq2gjzPVl3EBrwh6W2F2AvyBFABY,799
|
|
4
|
-
lamin_cli/_load.py,sha256=n0_qHTCXsZQIUy87rVdPvT89fx60N-KZgn_dL8nsvEA,5523
|
|
5
|
-
lamin_cli/_migration.py,sha256=xTbad6aDUwuK0QvWCmDW3f8-xaqwisS-Cqf-LbD1WRI,1071
|
|
6
|
-
lamin_cli/_save.py,sha256=X5Rn1rmvtoKz10oZXWWDK3XmNchFhruQ_is-sE8Q-BM,6575
|
|
7
|
-
lamin_cli/_settings.py,sha256=iS37mcQUHKRWxi2sHnAojEI6sWk3w232qwG-GeY2_Qc,1141
|
|
8
|
-
lamin_cli-0.21.4.dist-info/entry_points.txt,sha256=Qms85i9cZPlu-U7RnVZhFsF7vJ9gaLZUFkCjcGcXTpg,49
|
|
9
|
-
lamin_cli-0.21.4.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
10
|
-
lamin_cli-0.21.4.dist-info/WHEEL,sha256=Sgu64hAMa6g5FdzHxXv9Xdse9yxpGGMeagVtPMWpJQY,99
|
|
11
|
-
lamin_cli-0.21.4.dist-info/METADATA,sha256=tWTkIflVJ0CM_3pwig0RnfF0eddeR6LntJKyf2_GI5c,338
|
|
12
|
-
lamin_cli-0.21.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|