mango-lib 0.1.0__tar.gz → 0.3.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.
- mango_lib-0.3.0/PKG-INFO +60 -0
- mango_lib-0.3.0/README.md +42 -0
- mango_lib-0.3.0/pyproject.toml +42 -0
- mango_lib-0.3.0/pyproject.toml.orig +42 -0
- mango_lib-0.3.0/src/mango_lib/__init__.py +4 -0
- mango_lib-0.3.0/src/mango_lib/archive/mango_package.py +344 -0
- mango_lib-0.3.0/src/mango_lib/archive/mango_tar.py +685 -0
- mango_lib-0.3.0/src/mango_lib/cli/__init__.py +3 -0
- mango_lib-0.3.0/src/mango_lib/cli/base.py +48 -0
- mango_lib-0.3.0/src/mango_lib/debug.py +62 -0
- mango_lib-0.3.0/src/mango_lib/irods/auth.py +59 -0
- mango_lib-0.3.0/src/mango_lib/irods/query.py +61 -0
- mango_lib-0.3.0/src/mango_lib/irods/user.py +38 -0
- mango_lib-0.3.0/src/mango_lib/irods/util.py +59 -0
- mango_lib-0.3.0/src/mango_lib/jinja/__init__.py +301 -0
- mango_lib-0.3.0/src/mango_lib/patterns.py +113 -0
- mango_lib-0.1.0/PKG-INFO +0 -10
- mango_lib-0.1.0/pyproject.toml +0 -17
- mango_lib-0.1.0/src/mango_lib/__init__.py +0 -2
- /mango_lib-0.1.0/README.md → /mango_lib-0.3.0/src/mango_lib/archive/__init__.py +0 -0
- /mango_lib-0.1.0/src/mango_lib/metadata.py → /mango_lib-0.3.0/src/mango_lib/irods/__init__.py +0 -0
- /mango_lib-0.1.0/src/mango_lib/path_patterns.py → /mango_lib-0.3.0/src/mango_lib/metadata/__init__.py +0 -0
- {mango_lib-0.1.0 → mango_lib-0.3.0}/src/mango_lib/py.typed +0 -0
mango_lib-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: mango-lib
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Library of utility functions used across ManGO Portal, ManGO Flow and ManGO Ingest amongst others
|
|
5
|
+
Author: Paul Borgermans, Mariana Montes
|
|
6
|
+
Author-email: Paul Borgermans <paul.borgermans@kuleuven.be>, Mariana Montes <mariana.montes@kuleuven.be>
|
|
7
|
+
Requires-Dist: click>=8.1
|
|
8
|
+
Requires-Dist: humanize>=4.15.0
|
|
9
|
+
Requires-Dist: jinja2>=3.1.6
|
|
10
|
+
Requires-Dist: mango-mdconverter>=0.0.10
|
|
11
|
+
Requires-Dist: nh3>=0.3.5
|
|
12
|
+
Requires-Dist: python-irodsclient>=3.2.0
|
|
13
|
+
Requires-Dist: rich>=15.0.0
|
|
14
|
+
Requires-Dist: mango-mdconverter>=0.0.10 ; extra == 'mango-mdconverter'
|
|
15
|
+
Requires-Python: >=3.12
|
|
16
|
+
Provides-Extra: mango-mdconverter
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# ManGO Lib
|
|
20
|
+
|
|
21
|
+
This package provides common or useful tools that are in part extracted from the ManGO family of applications and frameworks developed and used by KULeuven ICTS-FOZ-RDM team.
|
|
22
|
+
|
|
23
|
+
> [!WARNING]
|
|
24
|
+
> ManGO Lib is currently under heavy development as it is the cornerstone for the ongoing ManGO platform refactoring
|
|
25
|
+
> Therefore there is no PyPi installable package (yet)
|
|
26
|
+
|
|
27
|
+
## Documentation
|
|
28
|
+
|
|
29
|
+
The documentation is curated in a dedicated repository covering the entire ManGO family and is located at:
|
|
30
|
+
|
|
31
|
+
...
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
### With `uv`
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
uv add mango-lib
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### With `pip`
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install mango-lib
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Running the unit tests
|
|
48
|
+
|
|
49
|
+
Requirements: `uv` and `pytest`
|
|
50
|
+
|
|
51
|
+
- Checkout the repository locally
|
|
52
|
+
- From the top level run:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
uv run pytest -v -s
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# ManGO Lib
|
|
2
|
+
|
|
3
|
+
This package provides common or useful tools that are in part extracted from the ManGO family of applications and frameworks developed and used by KULeuven ICTS-FOZ-RDM team.
|
|
4
|
+
|
|
5
|
+
> [!WARNING]
|
|
6
|
+
> ManGO Lib is currently under heavy development as it is the cornerstone for the ongoing ManGO platform refactoring
|
|
7
|
+
> Therefore there is no PyPi installable package (yet)
|
|
8
|
+
|
|
9
|
+
## Documentation
|
|
10
|
+
|
|
11
|
+
The documentation is curated in a dedicated repository covering the entire ManGO family and is located at:
|
|
12
|
+
|
|
13
|
+
...
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
### With `uv`
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
uv add mango-lib
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### With `pip`
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install mango-lib
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Running the unit tests
|
|
30
|
+
|
|
31
|
+
Requirements: `uv` and `pytest`
|
|
32
|
+
|
|
33
|
+
- Checkout the repository locally
|
|
34
|
+
- From the top level run:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
uv run pytest -v -s
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "mango-lib"
|
|
3
|
+
version = "0.3.0"
|
|
4
|
+
description = "Library of utility functions used across ManGO Portal, ManGO Flow and ManGO Ingest amongst others"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"click>=8.1",
|
|
9
|
+
"humanize>=4.15.0",
|
|
10
|
+
"jinja2>=3.1.6",
|
|
11
|
+
"mango-mdconverter>=0.0.10",
|
|
12
|
+
"nh3>=0.3.5",
|
|
13
|
+
"python-irodsclient>=3.2.0",
|
|
14
|
+
"rich>=15.0.0",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[[project.authors]]
|
|
18
|
+
name = "Paul Borgermans"
|
|
19
|
+
email = "paul.borgermans@kuleuven.be"
|
|
20
|
+
|
|
21
|
+
[[project.authors]]
|
|
22
|
+
name = "Mariana Montes"
|
|
23
|
+
email = "mariana.montes@kuleuven.be"
|
|
24
|
+
|
|
25
|
+
[project.scripts]
|
|
26
|
+
mango = "mango_lib.cli:mango"
|
|
27
|
+
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
mango-mdconverter = ["mango-mdconverter>=0.0.10"]
|
|
30
|
+
|
|
31
|
+
[build-system]
|
|
32
|
+
requires = ["uv_build>=0.11,<0.12.0"]
|
|
33
|
+
build-backend = "uv_build"
|
|
34
|
+
|
|
35
|
+
[tool.pytest.ini_options]
|
|
36
|
+
pythonpath = ["src"]
|
|
37
|
+
|
|
38
|
+
[dependency-groups]
|
|
39
|
+
dev = [
|
|
40
|
+
"pytest>=9.0.3,<9.1.0",
|
|
41
|
+
"pytest-cases>=3.10.1",
|
|
42
|
+
]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
|
|
2
|
+
[project]
|
|
3
|
+
name = "mango-lib"
|
|
4
|
+
version = "0.3.0"
|
|
5
|
+
description = "Library of utility functions used across ManGO Portal, ManGO Flow and ManGO Ingest amongst others"
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
authors = [
|
|
8
|
+
{ name = "Paul Borgermans", email = "paul.borgermans@kuleuven.be" },
|
|
9
|
+
{ name = "Mariana Montes", email = "mariana.montes@kuleuven.be"}
|
|
10
|
+
]
|
|
11
|
+
requires-python = ">=3.12"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"click>=8.1",
|
|
14
|
+
"humanize>=4.15.0",
|
|
15
|
+
"jinja2>=3.1.6",
|
|
16
|
+
"mango-mdconverter>=0.0.10",
|
|
17
|
+
"nh3>=0.3.5",
|
|
18
|
+
"python-irodsclient>=3.2.0",
|
|
19
|
+
"rich>=15.0.0",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.scripts]
|
|
23
|
+
mango = "mango_lib.cli:mango"
|
|
24
|
+
|
|
25
|
+
[project.optional-dependencies]
|
|
26
|
+
mango-mdconverter = [
|
|
27
|
+
"mango-mdconverter>=0.0.10",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
[build-system]
|
|
32
|
+
requires = ["uv_build>=0.11,<0.12.0"]
|
|
33
|
+
build-backend = "uv_build"
|
|
34
|
+
|
|
35
|
+
[tool.pytest.ini_options]
|
|
36
|
+
pythonpath = ["src"]
|
|
37
|
+
|
|
38
|
+
[dependency-groups]
|
|
39
|
+
dev = [
|
|
40
|
+
"pytest>=9.0.3,<9.1.0",
|
|
41
|
+
"pytest-cases>=3.10.1",
|
|
42
|
+
]
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
"""Attempt at isolating the packaging function in a module with everything it needs
|
|
2
|
+
to run from local/iRODS to iRODS, with metadata if the source is iRODS,
|
|
3
|
+
WITHOUT celery or steps or any of that.
|
|
4
|
+
This should be added to the orchestrator before providing the orchestrator to the function.
|
|
5
|
+
The only compulsory arguments of the packaging function are
|
|
6
|
+
- an iterator of files
|
|
7
|
+
- a buffered writer to tar things into
|
|
8
|
+
- a base path to compute the relative path
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import functools
|
|
12
|
+
import hashlib
|
|
13
|
+
import io
|
|
14
|
+
import json
|
|
15
|
+
import pathlib
|
|
16
|
+
import tarfile
|
|
17
|
+
from collections.abc import Callable, Generator, Iterable
|
|
18
|
+
|
|
19
|
+
from irods.collection import iRODSCollection
|
|
20
|
+
from irods.data_object import iRODSDataObject
|
|
21
|
+
from irods.session import iRODSSession
|
|
22
|
+
from mango_mdconverter.md2dict import convert_metadata_to_dict
|
|
23
|
+
|
|
24
|
+
from mango_lib.archive import mango_tar
|
|
25
|
+
|
|
26
|
+
MANIFEST_PREFIX = "data"
|
|
27
|
+
MANIFEST_NAME = "manifest-sha256.txt"
|
|
28
|
+
LOCAL_FOLDER = pathlib.Path(".")
|
|
29
|
+
|
|
30
|
+
MANIFEST_PATH = LOCAL_FOLDER / MANIFEST_NAME
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def tar_prefix(dataset):
|
|
34
|
+
return f"{dataset}/bag/data"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def add_irods_metadata_to_tar(
|
|
38
|
+
orchestrator: mango_tar.TarOrchestrator,
|
|
39
|
+
item: mango_tar.TarInputItem,
|
|
40
|
+
alt_tar: io.BufferedWriter | None = None,
|
|
41
|
+
):
|
|
42
|
+
if not isinstance(item, mango_tar.iRODSInputItem):
|
|
43
|
+
return
|
|
44
|
+
metadata = convert_metadata_to_dict(item.item.metadata.items())
|
|
45
|
+
|
|
46
|
+
if len(metadata) == 0:
|
|
47
|
+
# skip further processing
|
|
48
|
+
print(f"NO metadata for {item.name}")
|
|
49
|
+
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
bytes_buff = json.dumps(metadata).encode()
|
|
53
|
+
metadata_base_path = (
|
|
54
|
+
item.item.path
|
|
55
|
+
if isinstance(item.item, iRODSDataObject)
|
|
56
|
+
else str(pathlib.Path(item.item.path) / item.item.name)
|
|
57
|
+
)
|
|
58
|
+
tar_input_item = mango_tar.BytesInputItem(
|
|
59
|
+
bytes_buff,
|
|
60
|
+
needs_checksum=True,
|
|
61
|
+
path=f"{metadata_base_path}.metadata.json",
|
|
62
|
+
prefix=item.prefix,
|
|
63
|
+
alt_prefix=item.alt_prefix,
|
|
64
|
+
rel_path=item.rel_path,
|
|
65
|
+
)
|
|
66
|
+
mango_tar.record_checksum(orchestrator, tar_input_item)
|
|
67
|
+
mango_tar.add_bytes_item_to_tar(orchestrator.dest_tar, tar_input_item)
|
|
68
|
+
|
|
69
|
+
if alt_tar:
|
|
70
|
+
mango_tar.add_bytes_item_to_tar(alt_tar, tar_input_item)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def bag_tar(
|
|
74
|
+
orchestrator: mango_tar.TarOrchestrator,
|
|
75
|
+
dataset_name: str,
|
|
76
|
+
manifest_path: pathlib.Path = MANIFEST_PATH,
|
|
77
|
+
rel_path: str = str(LOCAL_FOLDER),
|
|
78
|
+
):
|
|
79
|
+
manifest_tar_input = mango_tar.FileInputItem(
|
|
80
|
+
manifest_path, rel_path=rel_path, prefix=f"{dataset_name}/bag"
|
|
81
|
+
)
|
|
82
|
+
with manifest_path.open("rb") as manifest_fp:
|
|
83
|
+
mango_tar.stream_fp_to_tar(
|
|
84
|
+
manifest_fp,
|
|
85
|
+
input_item=manifest_tar_input,
|
|
86
|
+
tar_dest=orchestrator.dest_tar,
|
|
87
|
+
read_buffer_size=1048576,
|
|
88
|
+
orchestrator=orchestrator,
|
|
89
|
+
)
|
|
90
|
+
orchestrator.at_current_file_end(
|
|
91
|
+
item=manifest_tar_input, dest_tar=orchestrator.dest_tar, callbacks=None
|
|
92
|
+
)
|
|
93
|
+
bagit_contents = "BagIt version 0.97\nTag-File-Character-Encoding: UTF-8\n".encode()
|
|
94
|
+
bagit_item = mango_tar.BytesInputItem(
|
|
95
|
+
bagit_contents, needs_checksum=False, path=f"{dataset_name}/bag/bagit.txt"
|
|
96
|
+
)
|
|
97
|
+
mango_tar.add_bytes_item_to_tar(orchestrator.dest_tar, bagit_item)
|
|
98
|
+
orchestrator.at_current_file_end(
|
|
99
|
+
item=bagit_item, dest_tar=orchestrator.dest_tar, callbacks=None
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def packaging_orchestrator(
|
|
104
|
+
orchestrator: mango_tar.TarOrchestrator = None,
|
|
105
|
+
manifest_path: pathlib.Path = MANIFEST_PATH,
|
|
106
|
+
dataset_name: str = "",
|
|
107
|
+
metadata_tar: io.BufferedWriter | None = None,
|
|
108
|
+
):
|
|
109
|
+
|
|
110
|
+
if not isinstance(orchestrator, mango_tar.TarOrchestrator):
|
|
111
|
+
orchestrator = mango_tar.TarOrchestrator() # initializing!
|
|
112
|
+
|
|
113
|
+
# CHECKSUMS FOR MANIFEST
|
|
114
|
+
orchestrator.add_callback("file_end", "checksum-save", mango_tar.record_checksum)
|
|
115
|
+
|
|
116
|
+
flush_checksums = functools.partial(
|
|
117
|
+
mango_tar.flush_checksums,
|
|
118
|
+
manifest_path=manifest_path,
|
|
119
|
+
format="bagit",
|
|
120
|
+
name_field="alt_name",
|
|
121
|
+
)
|
|
122
|
+
orchestrator.add_callback("delay_point", "update-manifest", flush_checksums)
|
|
123
|
+
|
|
124
|
+
orchestrator.add_callback(
|
|
125
|
+
"end",
|
|
126
|
+
"finish-manifest",
|
|
127
|
+
flush_checksums,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
orchestrator.add_callback(
|
|
131
|
+
"end",
|
|
132
|
+
"make-bagit",
|
|
133
|
+
functools.partial(
|
|
134
|
+
bag_tar,
|
|
135
|
+
dataset_name=dataset_name,
|
|
136
|
+
manifest_path=manifest_path,
|
|
137
|
+
rel_path=str(manifest_path.parent),
|
|
138
|
+
),
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# add metadata
|
|
142
|
+
add_metadata_partial = functools.partial(
|
|
143
|
+
add_irods_metadata_to_tar,
|
|
144
|
+
alt_tar=metadata_tar,
|
|
145
|
+
)
|
|
146
|
+
orchestrator.add_callback("file_end", "add_metadata", add_metadata_partial)
|
|
147
|
+
orchestrator.add_callback(
|
|
148
|
+
"collection_item", "add_collection_metadata", add_metadata_partial
|
|
149
|
+
)
|
|
150
|
+
orchestrator.add_callback("abort", "flush-checksums", flush_checksums)
|
|
151
|
+
orchestrator.add_callback("exception", "flush-checksums", flush_checksums)
|
|
152
|
+
|
|
153
|
+
return orchestrator
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def parse_file_iterator(
|
|
157
|
+
file_iterator: Iterable, rel_path: str, dataset_name: str
|
|
158
|
+
) -> Generator[mango_tar.TarInputItem, None, None]:
|
|
159
|
+
for file in file_iterator:
|
|
160
|
+
if isinstance(file, mango_tar.TarInputItem):
|
|
161
|
+
yield file
|
|
162
|
+
elif isinstance(file, iRODSDataObject):
|
|
163
|
+
class_name = mango_tar.iRODSInputItem
|
|
164
|
+
elif isinstance(file, pathlib.Path):
|
|
165
|
+
class_name = mango_tar.FileInputItem
|
|
166
|
+
else:
|
|
167
|
+
raise TypeError(f"File type not supported for: {file}")
|
|
168
|
+
yield class_name(
|
|
169
|
+
file,
|
|
170
|
+
needs_checksum=True,
|
|
171
|
+
rel_path=rel_path,
|
|
172
|
+
prefix=tar_prefix(dataset_name),
|
|
173
|
+
alt_prefix=MANIFEST_PREFIX,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def parse_folder_iterator(
|
|
178
|
+
folder_iterator: Iterable, rel_path: str, dataset_name: str
|
|
179
|
+
) -> Generator[mango_tar.iRODSInputItem, None, None]:
|
|
180
|
+
# if isinstance(folder_iterator, iRODSTarInputJSONLReader):
|
|
181
|
+
# return folder_iterator.get_next_object()
|
|
182
|
+
for folder in folder_iterator:
|
|
183
|
+
if not isinstance(folder, iRODSCollection):
|
|
184
|
+
raise TypeError("Item must be an iRODS Collection")
|
|
185
|
+
yield mango_tar.iRODSInputItem(
|
|
186
|
+
folder,
|
|
187
|
+
rel_path=rel_path,
|
|
188
|
+
prefix=tar_prefix(dataset_name),
|
|
189
|
+
alt_prefix=MANIFEST_PREFIX,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def package_dataset(
|
|
194
|
+
file_iterator: Iterable,
|
|
195
|
+
dest_tar_object: iRODSDataObject | pathlib.Path,
|
|
196
|
+
base_path: str, # to compute relative paths
|
|
197
|
+
dataset_name: str = "dummy_dataset",
|
|
198
|
+
folder_iterator: Iterable | None = None,
|
|
199
|
+
orchestrator: mango_tar.TarOrchestrator | None = None,
|
|
200
|
+
local_folder: pathlib.Path = LOCAL_FOLDER,
|
|
201
|
+
manifest_name: str = MANIFEST_NAME,
|
|
202
|
+
add_metadata_tar: bool = False,
|
|
203
|
+
last_good_write: dict | None = None,
|
|
204
|
+
# rocrate_source=None,
|
|
205
|
+
return_orchestrator: bool = False,
|
|
206
|
+
) -> tuple:
|
|
207
|
+
manifest_path = local_folder / manifest_name
|
|
208
|
+
with manifest_path.open("w"):
|
|
209
|
+
pass
|
|
210
|
+
|
|
211
|
+
files = parse_file_iterator(file_iterator, base_path, dataset_name)
|
|
212
|
+
collections = parse_folder_iterator(folder_iterator, base_path, dataset_name)
|
|
213
|
+
|
|
214
|
+
# metadata tar
|
|
215
|
+
if add_metadata_tar:
|
|
216
|
+
alt_metadata_tar_path = local_folder / f"{dataset_name}_metadata.tar"
|
|
217
|
+
alt_metadata_tar = alt_metadata_tar_path.open("wb")
|
|
218
|
+
else:
|
|
219
|
+
alt_metadata_tar = None
|
|
220
|
+
|
|
221
|
+
# set up orchestrator, from scratch if none is provided
|
|
222
|
+
orchestrator = packaging_orchestrator(
|
|
223
|
+
orchestrator=orchestrator,
|
|
224
|
+
manifest_path=manifest_path,
|
|
225
|
+
dataset_name=dataset_name,
|
|
226
|
+
metadata_tar=alt_metadata_tar,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
restart_output = mango_tar.restartable_tar(
|
|
230
|
+
object_iterator=files,
|
|
231
|
+
collection_iterator=collections,
|
|
232
|
+
dest_tar_object=dest_tar_object,
|
|
233
|
+
orchestrator=orchestrator,
|
|
234
|
+
last_good_write=last_good_write,
|
|
235
|
+
local_folder=local_folder,
|
|
236
|
+
)
|
|
237
|
+
if alt_metadata_tar is not None:
|
|
238
|
+
alt_metadata_tar.close()
|
|
239
|
+
return (
|
|
240
|
+
(restart_output, orchestrator)
|
|
241
|
+
if return_orchestrator
|
|
242
|
+
else (restart_output, None)
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def compute_sha256(
|
|
247
|
+
fp,
|
|
248
|
+
chunk_size: int = 16 * 1024 * 1024,
|
|
249
|
+
stream_callback: Callable | None = None,
|
|
250
|
+
):
|
|
251
|
+
"""Compute sha256 checksum with a filepointer as input"""
|
|
252
|
+
hash_sha256 = hashlib.sha256()
|
|
253
|
+
for chunk in iter(lambda: fp.read(chunk_size), b""):
|
|
254
|
+
hash_sha256.update(chunk)
|
|
255
|
+
if stream_callback:
|
|
256
|
+
stream_callback(len(chunk))
|
|
257
|
+
return hash_sha256.hexdigest()
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def compute_checksums_from_irods_tar(
|
|
261
|
+
irods_session: iRODSSession,
|
|
262
|
+
path_to_tar: str,
|
|
263
|
+
output_manifest_path: str,
|
|
264
|
+
stream_callback: Callable | None = None,
|
|
265
|
+
file_callback: Callable | None = None,
|
|
266
|
+
):
|
|
267
|
+
"""Finish Me"""
|
|
268
|
+
|
|
269
|
+
# @todo implement and do something with the callbacks
|
|
270
|
+
# or implement something like the tar orchestrator?
|
|
271
|
+
with irods_session.data_objects.open(path_to_tar, "r") as irods_tar_fp:
|
|
272
|
+
with tarfile.open(fileobj=irods_tar_fp, mode="r") as tar_fp:
|
|
273
|
+
with pathlib.Path(output_manifest_path).open("w") as output_manifest_fp:
|
|
274
|
+
while tar_info := tar_fp.next():
|
|
275
|
+
if tar_info.isfile():
|
|
276
|
+
with tar_fp.extractfile(tar_info) as fp:
|
|
277
|
+
computed_hash = compute_sha256(
|
|
278
|
+
fp, stream_callback=stream_callback
|
|
279
|
+
)
|
|
280
|
+
output_manifest_fp.write(
|
|
281
|
+
f"{computed_hash} {tar_info.name}\n"
|
|
282
|
+
)
|
|
283
|
+
if file_callback:
|
|
284
|
+
file_callback(
|
|
285
|
+
tar_info.name, tar_info.size, computed_hash
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def compare_checksums_from_files(
|
|
290
|
+
from_input: str, from_tar: str, input_prefix="", tar_prefix=""
|
|
291
|
+
):
|
|
292
|
+
"""compare checksums from input lists of which the elements are not necessarily in the
|
|
293
|
+
same order or even the same list size
|
|
294
|
+
|
|
295
|
+
"""
|
|
296
|
+
# Using more_itertools.filter_map
|
|
297
|
+
cs_input_d = {} # {path: checksum}
|
|
298
|
+
cs_tar_d = {}
|
|
299
|
+
with pathlib.Path(from_input).open("r") as input_mf_fp:
|
|
300
|
+
for line in input_mf_fp:
|
|
301
|
+
item = line.rstrip().split(" ", 1) # path may also contain whitespaces
|
|
302
|
+
if len(item) == 2:
|
|
303
|
+
cs_input_d[input_prefix + item[1]] = item[0]
|
|
304
|
+
with pathlib.Path(from_tar).open("r") as tar_mf_fp:
|
|
305
|
+
for line in tar_mf_fp:
|
|
306
|
+
item = line.rstrip().split(" ", 1)
|
|
307
|
+
if len(item) == 2:
|
|
308
|
+
cs_tar_d[tar_prefix + item[1]] = item[0]
|
|
309
|
+
|
|
310
|
+
not_found = {}
|
|
311
|
+
mis_match = {}
|
|
312
|
+
|
|
313
|
+
for path, checksum in cs_input_d.items():
|
|
314
|
+
tar_checksum = cs_tar_d.get(path, None)
|
|
315
|
+
if tar_checksum:
|
|
316
|
+
if checksum != tar_checksum:
|
|
317
|
+
mis_match[path] = f"{checksum} {tar_checksum}"
|
|
318
|
+
else:
|
|
319
|
+
not_found[path] = checksum
|
|
320
|
+
|
|
321
|
+
if not_found or mis_match:
|
|
322
|
+
# write the problematic paths to a file for later inspection
|
|
323
|
+
message = ""
|
|
324
|
+
if not_found:
|
|
325
|
+
with (pathlib.Path(from_input).parent / "checksums_not_found.txt").open(
|
|
326
|
+
"w"
|
|
327
|
+
) as nf_fp:
|
|
328
|
+
for item in not_found:
|
|
329
|
+
nf_fp.write(f"{not_found[item]} {item}\n")
|
|
330
|
+
message += f"{len(not_found)} items were not found. "
|
|
331
|
+
if mis_match:
|
|
332
|
+
with (pathlib.Path(from_input).parent / "checksums_mis_match.txt").open(
|
|
333
|
+
"w"
|
|
334
|
+
) as missing_fp:
|
|
335
|
+
for item in mis_match:
|
|
336
|
+
# The format is first the checksum as read + SPACE + checksum from tar + PATH
|
|
337
|
+
missing_fp.write(f"{mis_match[item]} {item}\n")
|
|
338
|
+
message += (
|
|
339
|
+
f"{len(mis_match)} items were found but the checksum does not match."
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
raise ValueError(f"The match is not perfect: {message}")
|
|
343
|
+
|
|
344
|
+
return True
|