cloudos-cli 2.38.3__tar.gz → 2.40.0__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.
- {cloudos_cli-2.38.3 → cloudos_cli-2.40.0}/PKG-INFO +3 -3
- {cloudos_cli-2.38.3 → cloudos_cli-2.40.0}/README.md +2 -2
- {cloudos_cli-2.38.3 → cloudos_cli-2.40.0}/cloudos_cli/__main__.py +38 -40
- cloudos_cli-2.40.0/cloudos_cli/_version.py +1 -0
- {cloudos_cli-2.38.3 → cloudos_cli-2.40.0}/cloudos_cli/datasets/datasets.py +89 -1
- {cloudos_cli-2.38.3 → cloudos_cli-2.40.0}/cloudos_cli.egg-info/PKG-INFO +3 -3
- cloudos_cli-2.38.3/cloudos_cli/_version.py +0 -1
- {cloudos_cli-2.38.3 → cloudos_cli-2.40.0}/LICENSE +0 -0
- {cloudos_cli-2.38.3 → cloudos_cli-2.40.0}/cloudos_cli/__init__.py +0 -0
- {cloudos_cli-2.38.3 → cloudos_cli-2.40.0}/cloudos_cli/clos.py +0 -0
- {cloudos_cli-2.38.3 → cloudos_cli-2.40.0}/cloudos_cli/configure/__init__.py +0 -0
- {cloudos_cli-2.38.3 → cloudos_cli-2.40.0}/cloudos_cli/configure/configure.py +0 -0
- {cloudos_cli-2.38.3 → cloudos_cli-2.40.0}/cloudos_cli/datasets/__init__.py +0 -0
- {cloudos_cli-2.38.3 → cloudos_cli-2.40.0}/cloudos_cli/import_wf/__init__.py +0 -0
- {cloudos_cli-2.38.3 → cloudos_cli-2.40.0}/cloudos_cli/import_wf/import_wf.py +0 -0
- {cloudos_cli-2.38.3 → cloudos_cli-2.40.0}/cloudos_cli/jobs/__init__.py +0 -0
- {cloudos_cli-2.38.3 → cloudos_cli-2.40.0}/cloudos_cli/jobs/job.py +0 -0
- {cloudos_cli-2.38.3 → cloudos_cli-2.40.0}/cloudos_cli/link/__init__.py +0 -0
- {cloudos_cli-2.38.3 → cloudos_cli-2.40.0}/cloudos_cli/link/link.py +0 -0
- {cloudos_cli-2.38.3 → cloudos_cli-2.40.0}/cloudos_cli/queue/__init__.py +0 -0
- {cloudos_cli-2.38.3 → cloudos_cli-2.40.0}/cloudos_cli/queue/queue.py +0 -0
- {cloudos_cli-2.38.3 → cloudos_cli-2.40.0}/cloudos_cli/utils/__init__.py +0 -0
- {cloudos_cli-2.38.3 → cloudos_cli-2.40.0}/cloudos_cli/utils/array_job.py +0 -0
- {cloudos_cli-2.38.3 → cloudos_cli-2.40.0}/cloudos_cli/utils/cloud.py +0 -0
- {cloudos_cli-2.38.3 → cloudos_cli-2.40.0}/cloudos_cli/utils/details.py +0 -0
- {cloudos_cli-2.38.3 → cloudos_cli-2.40.0}/cloudos_cli/utils/errors.py +0 -0
- {cloudos_cli-2.38.3 → cloudos_cli-2.40.0}/cloudos_cli/utils/requests.py +0 -0
- {cloudos_cli-2.38.3 → cloudos_cli-2.40.0}/cloudos_cli/utils/resources.py +0 -0
- {cloudos_cli-2.38.3 → cloudos_cli-2.40.0}/cloudos_cli.egg-info/SOURCES.txt +0 -0
- {cloudos_cli-2.38.3 → cloudos_cli-2.40.0}/cloudos_cli.egg-info/dependency_links.txt +0 -0
- {cloudos_cli-2.38.3 → cloudos_cli-2.40.0}/cloudos_cli.egg-info/entry_points.txt +0 -0
- {cloudos_cli-2.38.3 → cloudos_cli-2.40.0}/cloudos_cli.egg-info/requires.txt +0 -0
- {cloudos_cli-2.38.3 → cloudos_cli-2.40.0}/cloudos_cli.egg-info/top_level.txt +0 -0
- {cloudos_cli-2.38.3 → cloudos_cli-2.40.0}/setup.cfg +0 -0
- {cloudos_cli-2.38.3 → cloudos_cli-2.40.0}/setup.py +0 -0
- {cloudos_cli-2.38.3 → cloudos_cli-2.40.0}/tests/__init__.py +0 -0
- {cloudos_cli-2.38.3 → cloudos_cli-2.40.0}/tests/functions_for_pytest.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cloudos_cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.40.0
|
|
4
4
|
Summary: Python package for interacting with CloudOS
|
|
5
5
|
Home-page: https://github.com/lifebit-ai/cloudos-cli
|
|
6
6
|
Author: David Piñeyro
|
|
@@ -1006,8 +1006,8 @@ If you require more information on the files and folder listed, you can use the
|
|
|
1006
1006
|
- Owner
|
|
1007
1007
|
- Size in human readable format
|
|
1008
1008
|
- Last updated
|
|
1009
|
-
-
|
|
1010
|
-
-
|
|
1009
|
+
- File Name (the file or folder name)
|
|
1010
|
+
- Storage Path
|
|
1011
1011
|
|
|
1012
1012
|
##### Moving files
|
|
1013
1013
|
|
|
@@ -971,8 +971,8 @@ If you require more information on the files and folder listed, you can use the
|
|
|
971
971
|
- Owner
|
|
972
972
|
- Size in human readable format
|
|
973
973
|
- Last updated
|
|
974
|
-
-
|
|
975
|
-
-
|
|
974
|
+
- File Name (the file or folder name)
|
|
975
|
+
- Storage Path
|
|
976
976
|
|
|
977
977
|
##### Moving files
|
|
978
978
|
|
|
@@ -2653,7 +2653,7 @@ def run_bash_array_job(ctx,
|
|
|
2653
2653
|
@click.option('--details',
|
|
2654
2654
|
help=('When selected, it prints the details of the listed files. ' +
|
|
2655
2655
|
'Details contains "Type", "Owner", "Size", "Last Updated", ' +
|
|
2656
|
-
'"
|
|
2656
|
+
'"File Name", "Storage Path".'),
|
|
2657
2657
|
is_flag=True)
|
|
2658
2658
|
@click.pass_context
|
|
2659
2659
|
def list_files(ctx,
|
|
@@ -2668,10 +2668,9 @@ def list_files(ctx,
|
|
|
2668
2668
|
details):
|
|
2669
2669
|
"""List contents of a path within a CloudOS workspace dataset."""
|
|
2670
2670
|
|
|
2671
|
-
# fallback to ctx default if profile not specified
|
|
2672
2671
|
profile = profile or ctx.default_map['datasets']['list'].get('profile')
|
|
2673
|
-
|
|
2674
2672
|
config_manager = ConfigurationProfile()
|
|
2673
|
+
|
|
2675
2674
|
required_dict = {
|
|
2676
2675
|
'apikey': True,
|
|
2677
2676
|
'workspace_id': True,
|
|
@@ -2679,28 +2678,25 @@ def list_files(ctx,
|
|
|
2679
2678
|
'project_name': False
|
|
2680
2679
|
}
|
|
2681
2680
|
|
|
2682
|
-
user_options = (
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
project_name=project_name
|
|
2696
|
-
)
|
|
2681
|
+
user_options = config_manager.load_profile_and_validate_data(
|
|
2682
|
+
ctx,
|
|
2683
|
+
INIT_PROFILE,
|
|
2684
|
+
CLOUDOS_URL,
|
|
2685
|
+
profile=profile,
|
|
2686
|
+
required_dict=required_dict,
|
|
2687
|
+
apikey=apikey,
|
|
2688
|
+
cloudos_url=cloudos_url,
|
|
2689
|
+
workspace_id=workspace_id,
|
|
2690
|
+
workflow_name=None,
|
|
2691
|
+
repository_platform=None,
|
|
2692
|
+
execution_platform=None,
|
|
2693
|
+
project_name=project_name
|
|
2697
2694
|
)
|
|
2698
|
-
|
|
2695
|
+
|
|
2699
2696
|
apikey = user_options['apikey']
|
|
2700
2697
|
cloudos_url = user_options['cloudos_url']
|
|
2701
2698
|
workspace_id = user_options['workspace_id']
|
|
2702
2699
|
project_name = user_options['project_name']
|
|
2703
|
-
|
|
2704
2700
|
verify_ssl = ssl_selector(disable_ssl_verification, ssl_cert)
|
|
2705
2701
|
|
|
2706
2702
|
datasets = Datasets(
|
|
@@ -2719,32 +2715,31 @@ def list_files(ctx,
|
|
|
2719
2715
|
contents = result.get("files", []) + result.get("folders", [])
|
|
2720
2716
|
|
|
2721
2717
|
if details:
|
|
2722
|
-
console = Console(width=None)
|
|
2723
|
-
|
|
2718
|
+
console = Console(width=None)
|
|
2724
2719
|
table = Table(show_header=True, header_style="bold white")
|
|
2725
2720
|
table.add_column("Type", style="cyan", no_wrap=True)
|
|
2726
2721
|
table.add_column("Owner", style="white")
|
|
2727
2722
|
table.add_column("Size", style="magenta")
|
|
2728
2723
|
table.add_column("Last Updated", style="green")
|
|
2729
|
-
table.add_column("
|
|
2730
|
-
table.add_column("
|
|
2724
|
+
table.add_column("File Name", style="bold", overflow="fold")
|
|
2725
|
+
table.add_column("Storage Path", style="dim", no_wrap=False, overflow="fold", ratio=2)
|
|
2731
2726
|
|
|
2732
2727
|
for item in contents:
|
|
2733
2728
|
is_folder = "folderType" in item or item.get("isDir", False)
|
|
2734
2729
|
type_ = "folder" if is_folder else "file"
|
|
2735
2730
|
|
|
2736
|
-
user = item.get("user")
|
|
2731
|
+
user = item.get("user", {})
|
|
2737
2732
|
if isinstance(user, dict):
|
|
2738
2733
|
name = user.get("name", "").strip()
|
|
2739
2734
|
surname = user.get("surname", "").strip()
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2735
|
+
else:
|
|
2736
|
+
name = surname = ""
|
|
2737
|
+
if name and surname:
|
|
2738
|
+
owner = f"{name} {surname}"
|
|
2739
|
+
elif name:
|
|
2740
|
+
owner = name
|
|
2741
|
+
elif surname:
|
|
2742
|
+
owner = surname
|
|
2748
2743
|
else:
|
|
2749
2744
|
owner = "-"
|
|
2750
2745
|
|
|
@@ -2754,14 +2749,17 @@ def list_files(ctx,
|
|
|
2754
2749
|
updated = item.get("updatedAt") or item.get("lastModified", "-")
|
|
2755
2750
|
filepath = item.get("name", "-")
|
|
2756
2751
|
|
|
2757
|
-
if
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
s3_path = f"s3://{
|
|
2752
|
+
if item.get("fileType") == "S3File" or item.get("folderType") == "S3Folder":
|
|
2753
|
+
bucket = item.get("s3BucketName")
|
|
2754
|
+
key = item.get("s3ObjectKey") or item.get("s3Prefix")
|
|
2755
|
+
s3_path = f"s3://{bucket}/{key}" if bucket and key else "-"
|
|
2756
|
+
elif item.get("fileType") == "AzureBlobFile" or item.get("folderType") == "AzureBlobFolder":
|
|
2757
|
+
account = item.get("blobStorageAccountName")
|
|
2758
|
+
container = item.get("blobContainerName")
|
|
2759
|
+
key = item.get("blobName") if item.get("fileType") == "AzureBlobFile" else item.get("blobPrefix")
|
|
2760
|
+
s3_path = f"az://{account}.blob.core.windows.net/{container}/{key}" if account and container and key else "-"
|
|
2761
2761
|
else:
|
|
2762
|
-
|
|
2763
|
-
s3_key = item.get("s3ObjectKey") or item.get("s3Prefix")
|
|
2764
|
-
s3_path = f"s3://{s3_bucket}/{s3_key}" if s3_bucket and s3_key else "-"
|
|
2762
|
+
s3_path = "-"
|
|
2765
2763
|
|
|
2766
2764
|
style = Style(color="blue", underline=True) if is_folder else None
|
|
2767
2765
|
table.add_row(type_, owner, size, updated, filepath, s3_path, style=style)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '2.40.0'
|
|
@@ -5,6 +5,7 @@ This is the main class for file explorer (datasets).
|
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from typing import Union
|
|
7
7
|
from cloudos_cli.clos import Cloudos
|
|
8
|
+
from cloudos_cli.utils.errors import BadRequestException
|
|
8
9
|
from cloudos_cli.utils.requests import retry_requests_get, retry_requests_put, retry_requests_post, retry_requests_delete
|
|
9
10
|
import json
|
|
10
11
|
|
|
@@ -127,6 +128,8 @@ class Datasets(Cloudos):
|
|
|
127
128
|
f' and an importsFile \'{importsfile}\' was not found')
|
|
128
129
|
else:
|
|
129
130
|
raise ValueError(f'[ERROR] No {name} element in {resource} was found')
|
|
131
|
+
|
|
132
|
+
|
|
130
133
|
def list_project_content(self):
|
|
131
134
|
"""
|
|
132
135
|
Fetch the information of the directories present in the projects.
|
|
@@ -150,6 +153,8 @@ class Datasets(Cloudos):
|
|
|
150
153
|
self.project_id,
|
|
151
154
|
self.workspace_id),
|
|
152
155
|
headers=headers, verify=self.verify)
|
|
156
|
+
if r.status_code >= 400:
|
|
157
|
+
raise BadRequestException(r)
|
|
153
158
|
raw = r.json()
|
|
154
159
|
datasets = raw.get("datasets", [])
|
|
155
160
|
# Normalize response
|
|
@@ -195,6 +200,8 @@ class Datasets(Cloudos):
|
|
|
195
200
|
folder_id,
|
|
196
201
|
self.workspace_id),
|
|
197
202
|
headers=headers, verify=self.verify)
|
|
203
|
+
if r.status_code >= 400:
|
|
204
|
+
raise BadRequestException(r)
|
|
198
205
|
return r.json()
|
|
199
206
|
|
|
200
207
|
def list_s3_folder_content(self, s3_bucket_name, s3_relative_path):
|
|
@@ -224,6 +231,8 @@ class Datasets(Cloudos):
|
|
|
224
231
|
s3_relative_path,
|
|
225
232
|
self.workspace_id),
|
|
226
233
|
headers=headers, verify=self.verify)
|
|
234
|
+
if r.status_code >= 400:
|
|
235
|
+
raise BadRequestException(r)
|
|
227
236
|
raw = r.json()
|
|
228
237
|
|
|
229
238
|
# Normalize response
|
|
@@ -265,8 +274,63 @@ class Datasets(Cloudos):
|
|
|
265
274
|
folder_id,
|
|
266
275
|
self.workspace_id),
|
|
267
276
|
headers=headers, verify=self.verify)
|
|
277
|
+
if r.status_code >= 400:
|
|
278
|
+
raise BadRequestException(r)
|
|
268
279
|
return r.json()
|
|
269
280
|
|
|
281
|
+
def list_azure_container_content(self, container_name: str, storage_account_name: str, path: str):
|
|
282
|
+
"""
|
|
283
|
+
List contents of an Azure Blob container path.
|
|
284
|
+
"""
|
|
285
|
+
headers = {
|
|
286
|
+
"Content-type": "application/json",
|
|
287
|
+
"apikey": self.apikey
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
url = f"{self.cloudos_url}/api/v1/data-access/azure/container-contents"
|
|
291
|
+
url += f"?containerName={container_name}&storageAccountName={storage_account_name}"
|
|
292
|
+
url += f"&path={path}&teamId={self.workspace_id}"
|
|
293
|
+
|
|
294
|
+
r = retry_requests_get(url, headers=headers, verify=self.verify)
|
|
295
|
+
if r.status_code >= 400:
|
|
296
|
+
raise BadRequestException(r)
|
|
297
|
+
raw = r.json()
|
|
298
|
+
|
|
299
|
+
# Normalize response to match existing expectations
|
|
300
|
+
normalized = {"folders": [], "files": []}
|
|
301
|
+
for item in raw.get("contents", []):
|
|
302
|
+
is_dir = item.get("isDir", False)
|
|
303
|
+
|
|
304
|
+
# Set a name field based on the last part of the blob path
|
|
305
|
+
path_str = item.get("path", "")
|
|
306
|
+
name = item.get("name") or path_str.rstrip("/").split("/")[-1]
|
|
307
|
+
|
|
308
|
+
# inject expected structure
|
|
309
|
+
if is_dir:
|
|
310
|
+
normalized["folders"].append({
|
|
311
|
+
"_id": item.get("_id"),
|
|
312
|
+
"name": name,
|
|
313
|
+
"folderType": "AzureBlobFolder",
|
|
314
|
+
"blobPrefix": path_str,
|
|
315
|
+
"blobContainerName": container_name,
|
|
316
|
+
"blobStorageAccountName": storage_account_name,
|
|
317
|
+
"kind": "Folder"
|
|
318
|
+
})
|
|
319
|
+
else:
|
|
320
|
+
normalized["files"].append({
|
|
321
|
+
"_id": item.get("_id"),
|
|
322
|
+
"name": name,
|
|
323
|
+
"fileType": "AzureBlobFile",
|
|
324
|
+
"blobName": path_str,
|
|
325
|
+
"blobContainerName": container_name,
|
|
326
|
+
"blobStorageAccountName": storage_account_name,
|
|
327
|
+
"sizeInBytes": item.get("size", 0),
|
|
328
|
+
"updatedAt": item.get("lastModified"),
|
|
329
|
+
"kind": "File"
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
return normalized
|
|
333
|
+
|
|
270
334
|
def list_folder_content(self, path=None):
|
|
271
335
|
"""
|
|
272
336
|
Wrapper to list contents of a CloudOS folder.
|
|
@@ -324,6 +388,21 @@ class Datasets(Cloudos):
|
|
|
324
388
|
path_depth += 1
|
|
325
389
|
break
|
|
326
390
|
|
|
391
|
+
elif folder_type == "AzureBlobFolder":
|
|
392
|
+
container_name = job_folder['blobContainerName']
|
|
393
|
+
storage_account_name = job_folder['blobStorageAccountName']
|
|
394
|
+
blob_prefix = job_folder['blobPrefix']
|
|
395
|
+
# trailing slash is mandatory for azure, otherwise it will not list the content of thefolde, just the folder
|
|
396
|
+
if not blob_prefix.endswith('/'):
|
|
397
|
+
blob_prefix += '/'
|
|
398
|
+
|
|
399
|
+
if path_depth == len(parts) - 1:
|
|
400
|
+
return self.list_azure_container_content(container_name, storage_account_name, blob_prefix)
|
|
401
|
+
else:
|
|
402
|
+
sub_path = '/'.join(parts[0:path_depth+1])
|
|
403
|
+
folder_content = self.list_folder_content(sub_path)
|
|
404
|
+
path_depth += 1
|
|
405
|
+
break
|
|
327
406
|
else:
|
|
328
407
|
raise ValueError(f"Unsupported folder type '{folder_type}' for path '{path}'")
|
|
329
408
|
|
|
@@ -366,6 +445,8 @@ class Datasets(Cloudos):
|
|
|
366
445
|
}
|
|
367
446
|
}
|
|
368
447
|
response = retry_requests_put(url, headers=headers, data=json.dumps(payload), verify=self.verify)
|
|
448
|
+
if response.status_code >= 400:
|
|
449
|
+
raise BadRequestException(response)
|
|
369
450
|
return response
|
|
370
451
|
|
|
371
452
|
def rename_item(self, item_id: str, new_name: str, kind: str):
|
|
@@ -403,6 +484,8 @@ class Datasets(Cloudos):
|
|
|
403
484
|
}
|
|
404
485
|
|
|
405
486
|
response = retry_requests_put(url, headers=headers, data=json.dumps(payload), verify=self.verify)
|
|
487
|
+
if response.status_code >= 400:
|
|
488
|
+
raise BadRequestException(response)
|
|
406
489
|
return response
|
|
407
490
|
|
|
408
491
|
def copy_item(self, item, destination_id, destination_kind):
|
|
@@ -446,7 +529,8 @@ class Datasets(Cloudos):
|
|
|
446
529
|
else:
|
|
447
530
|
raise ValueError(f"Unknown item type for copy: {item.get('name')}")
|
|
448
531
|
response = retry_requests_post(url, headers=headers, json=payload)
|
|
449
|
-
|
|
532
|
+
if response.status_code >= 400:
|
|
533
|
+
raise BadRequestException(response)
|
|
450
534
|
return response
|
|
451
535
|
|
|
452
536
|
def create_virtual_folder(self, name: str, parent_id: str, parent_kind: str):
|
|
@@ -487,6 +571,8 @@ class Datasets(Cloudos):
|
|
|
487
571
|
}
|
|
488
572
|
|
|
489
573
|
response = retry_requests_post(url, headers=headers, json=payload, verify=self.verify)
|
|
574
|
+
if response.status_code >= 400:
|
|
575
|
+
raise BadRequestException(response)
|
|
490
576
|
return response
|
|
491
577
|
|
|
492
578
|
def delete_item(self, item_id: str, kind: str):
|
|
@@ -517,4 +603,6 @@ class Datasets(Cloudos):
|
|
|
517
603
|
}
|
|
518
604
|
|
|
519
605
|
response = retry_requests_delete(url, headers=headers, verify=self.verify)
|
|
606
|
+
if response.status_code >= 400:
|
|
607
|
+
raise BadRequestException(response)
|
|
520
608
|
return response
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cloudos_cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.40.0
|
|
4
4
|
Summary: Python package for interacting with CloudOS
|
|
5
5
|
Home-page: https://github.com/lifebit-ai/cloudos-cli
|
|
6
6
|
Author: David Piñeyro
|
|
@@ -1006,8 +1006,8 @@ If you require more information on the files and folder listed, you can use the
|
|
|
1006
1006
|
- Owner
|
|
1007
1007
|
- Size in human readable format
|
|
1008
1008
|
- Last updated
|
|
1009
|
-
-
|
|
1010
|
-
-
|
|
1009
|
+
- File Name (the file or folder name)
|
|
1010
|
+
- Storage Path
|
|
1011
1011
|
|
|
1012
1012
|
##### Moving files
|
|
1013
1013
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = '2.38.3'
|
|
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
|