fm-weck 1.5.3__py3-none-any.whl → 1.6.0__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.
- fm_weck/__init__.py +1 -1
- fm_weck/capture.py +1 -1
- fm_weck/cli.py +107 -1
- fm_weck/engine.py +31 -4
- fm_weck/exceptions.py +4 -0
- fm_weck/image_mgr.py +66 -0
- fm_weck/resources/fm_tools/2ls.yml +22 -0
- fm_weck/resources/fm_tools/afltc.yml +59 -0
- fm_weck/resources/fm_tools/aise.yml +28 -1
- fm_weck/resources/fm_tools/aprove.yml +25 -0
- fm_weck/resources/fm_tools/brick.yml +25 -1
- fm_weck/resources/fm_tools/bubaak-split.yml +18 -0
- fm_weck/resources/fm_tools/bubaak.yml +10 -0
- fm_weck/resources/fm_tools/cbmc.yml +12 -0
- fm_weck/resources/fm_tools/cetfuzz.yml +21 -6
- fm_weck/resources/fm_tools/coastal.yml +12 -0
- fm_weck/resources/fm_tools/concurrentwitness2test.yml +25 -2
- fm_weck/resources/fm_tools/cooperace.yml +39 -0
- fm_weck/resources/fm_tools/coveritest.yml +22 -0
- fm_weck/resources/fm_tools/cpa-bam-bnb.yml +16 -1
- fm_weck/resources/fm_tools/cpa-bam-smg.yml +17 -1
- fm_weck/resources/fm_tools/cpa-lockator.yml +17 -1
- fm_weck/resources/fm_tools/cpa-witness2test.yml +13 -3
- fm_weck/resources/fm_tools/cpachecker.yml +150 -10
- fm_weck/resources/fm_tools/cpv.yml +48 -0
- fm_weck/resources/fm_tools/crux.yml +12 -0
- fm_weck/resources/fm_tools/cseq.yml +36 -9
- fm_weck/resources/fm_tools/dartagnan.yml +63 -3
- fm_weck/resources/fm_tools/dasa.yml +70 -0
- fm_weck/resources/fm_tools/deagle.yml +19 -1
- fm_weck/resources/fm_tools/divine.yml +13 -0
- fm_weck/resources/fm_tools/ebf.yml +17 -2
- fm_weck/resources/fm_tools/emergentheta.yml +51 -5
- fm_weck/resources/fm_tools/esbmc-incr.yml +59 -2
- fm_weck/resources/fm_tools/esbmc-kind.yml +59 -2
- fm_weck/resources/fm_tools/fdse.yml +31 -0
- fm_weck/resources/fm_tools/fizzer.yml +32 -3
- fm_weck/resources/fm_tools/frama-c-sv.yml +21 -2
- fm_weck/resources/fm_tools/fshell-witness2test.yml +13 -3
- fm_weck/resources/fm_tools/function-res.yml +64 -0
- fm_weck/resources/fm_tools/fusebmc-ia.yml +20 -6
- fm_weck/resources/fm_tools/fusebmc.yml +25 -4
- fm_weck/resources/fm_tools/gazer-theta.yml +16 -3
- fm_weck/resources/fm_tools/gdart-llvm.yml +16 -1
- fm_weck/resources/fm_tools/gdart.yml +49 -2
- fm_weck/resources/fm_tools/goblint-par.yml +66 -0
- fm_weck/resources/fm_tools/goblint.yml +239 -12
- fm_weck/resources/fm_tools/goblitch.yml +77 -0
- fm_weck/resources/fm_tools/graves-par.yml +4 -1
- fm_weck/resources/fm_tools/graves.yml +20 -5
- fm_weck/resources/fm_tools/gwit.yml +13 -3
- fm_weck/resources/fm_tools/hornix.yml +26 -4
- fm_weck/resources/fm_tools/hybridtiger.yml +12 -1
- fm_weck/resources/fm_tools/iekke.yml +73 -0
- fm_weck/resources/fm_tools/infer.yml +12 -0
- fm_weck/resources/fm_tools/java-ranger.yml +18 -0
- fm_weck/resources/fm_tools/jayhorn.yml +50 -4
- fm_weck/resources/fm_tools/jbmc.yml +17 -1
- fm_weck/resources/fm_tools/jcwit.yml +12 -2
- fm_weck/resources/fm_tools/jdart.yml +16 -1
- fm_weck/resources/fm_tools/jlisa.yml +105 -0
- fm_weck/resources/fm_tools/klee.yml +12 -1
- fm_weck/resources/fm_tools/kleef.yml +18 -0
- fm_weck/resources/fm_tools/korn.yml +23 -1
- fm_weck/resources/fm_tools/lazycseq.yml +16 -1
- fm_weck/resources/fm_tools/legion-symcc.yml +5 -1
- fm_weck/resources/fm_tools/legion.yml +7 -4
- fm_weck/resources/fm_tools/lf-checker.yml +16 -1
- fm_weck/resources/fm_tools/liv.yml +48 -3
- fm_weck/resources/fm_tools/locksmith.yml +16 -1
- fm_weck/resources/fm_tools/metaval++.yml +1 -1
- fm_weck/resources/fm_tools/metaval.yml +121 -6
- fm_weck/resources/fm_tools/mlb.yml +23 -1
- fm_weck/resources/fm_tools/mopsa.yml +100 -7
- fm_weck/resources/fm_tools/muval.yml +120 -0
- fm_weck/resources/fm_tools/nacpa.yml +57 -2
- fm_weck/resources/fm_tools/nitwit.yml +13 -3
- fm_weck/resources/fm_tools/ogchecker.yml +47 -0
- fm_weck/resources/fm_tools/owic.yml +16 -2
- fm_weck/resources/fm_tools/pesco.yml +19 -1
- fm_weck/resources/fm_tools/pichecker.yml +16 -1
- fm_weck/resources/fm_tools/pinaka.yml +14 -1
- fm_weck/resources/fm_tools/predatorhp.yml +12 -0
- fm_weck/resources/fm_tools/proton.yml +26 -1
- fm_weck/resources/fm_tools/prtest.yml +23 -0
- fm_weck/resources/fm_tools/pysvlib-linter.yml +77 -0
- fm_weck/resources/fm_tools/pysvlib-validator.yml +76 -0
- fm_weck/resources/fm_tools/racerf.yml +27 -4
- fm_weck/resources/fm_tools/re3ver.yml +78 -0
- fm_weck/resources/fm_tools/rizzer.yml +11 -1
- fm_weck/resources/fm_tools/schema.yml +185 -4
- fm_weck/resources/fm_tools/seal.yml +67 -0
- fm_weck/resources/fm_tools/sikraken.yml +25 -1
- fm_weck/resources/fm_tools/spf.yml +14 -1
- fm_weck/resources/fm_tools/sv-sanitizers.yml +46 -3
- fm_weck/resources/fm_tools/svf-svc.yml +33 -1
- fm_weck/resources/fm_tools/svlibchecker.yml +82 -0
- fm_weck/resources/fm_tools/swat.yml +30 -0
- fm_weck/resources/fm_tools/symbiotic-witch.yml +31 -6
- fm_weck/resources/fm_tools/symbiotic.yml +64 -1
- fm_weck/resources/fm_tools/testcoca.yml +53 -0
- fm_weck/resources/fm_tools/testcov.yml +97 -0
- fm_weck/resources/fm_tools/theta.yml +100 -2
- fm_weck/resources/fm_tools/thorn.yml +22 -1
- fm_weck/resources/fm_tools/tracerx-wp.yml +18 -0
- fm_weck/resources/fm_tools/tracerx.yml +22 -0
- fm_weck/resources/fm_tools/uautomizer.yml +186 -19
- fm_weck/resources/fm_tools/ugemcutter.yml +97 -9
- fm_weck/resources/fm_tools/ukojak.yml +55 -9
- fm_weck/resources/fm_tools/uparalizer.yml +78 -0
- fm_weck/resources/fm_tools/ureferee.yml +83 -13
- fm_weck/resources/fm_tools/utaipan.yml +61 -9
- fm_weck/resources/fm_tools/utestgen.yml +36 -0
- fm_weck/resources/fm_tools/veriabs.yml +12 -0
- fm_weck/resources/fm_tools/veriabsl.yml +16 -0
- fm_weck/resources/fm_tools/verioover.yml +16 -1
- fm_weck/resources/fm_tools/wasp-c.yml +16 -1
- fm_weck/resources/fm_tools/wit4java.yml +36 -4
- fm_weck/resources/fm_tools/witch.yml +29 -4
- fm_weck/resources/fm_tools/witnesslint.yml +111 -8
- fm_weck/resources/fm_tools/witnessmap.yml +29 -1
- fm_weck/zenodo.py +381 -0
- {fm_weck-1.5.3.dist-info → fm_weck-1.6.0.dist-info}/METADATA +2 -1
- fm_weck-1.6.0.dist-info/RECORD +188 -0
- {fm_weck-1.5.3.dist-info → fm_weck-1.6.0.dist-info}/WHEEL +1 -1
- fm_weck-1.5.3.dist-info/RECORD +0 -171
- {fm_weck-1.5.3.dist-info → fm_weck-1.6.0.dist-info}/entry_points.txt +0 -0
|
@@ -33,6 +33,43 @@ maintainers:
|
|
|
33
33
|
url: https://www.sosy-lab.org/people/lingsch-rosenfeld/
|
|
34
34
|
|
|
35
35
|
versions:
|
|
36
|
+
- version: "2.1.2-correctness-witnesses-v2"
|
|
37
|
+
doi: 10.5281/zenodo.17792484
|
|
38
|
+
benchexec_toolinfo_options: ["--witness", "${witness}", "--expectCorrectnessWitness", "--ignoreSelfLoops", "--expectedWitnessVersion", "2"]
|
|
39
|
+
required_ubuntu_packages:
|
|
40
|
+
- python3-lxml
|
|
41
|
+
- python3-jsonschema
|
|
42
|
+
- python3-yaml
|
|
43
|
+
- python3-pycparser
|
|
44
|
+
- python3-clang
|
|
45
|
+
- version: "2.1.2-violation-witnesses-v2"
|
|
46
|
+
doi: 10.5281/zenodo.17792484
|
|
47
|
+
benchexec_toolinfo_options: ["--witness", "${witness}", "--expectViolationWitness", "--ignoreSelfLoops", "--expectedWitnessVersion", "2"]
|
|
48
|
+
required_ubuntu_packages:
|
|
49
|
+
- python3-lxml
|
|
50
|
+
- python3-jsonschema
|
|
51
|
+
- python3-yaml
|
|
52
|
+
- python3-pycparser
|
|
53
|
+
- python3-clang
|
|
54
|
+
- version: "2.1.2-correctness-witnesses-v1"
|
|
55
|
+
doi: 10.5281/zenodo.17792484
|
|
56
|
+
benchexec_toolinfo_options: ["--witness", "${witness}", "--expectCorrectnessWitness", "--ignoreSelfLoops", "--expectedWitnessVersion", "1"]
|
|
57
|
+
required_ubuntu_packages:
|
|
58
|
+
- python3-lxml
|
|
59
|
+
- python3-jsonschema
|
|
60
|
+
- python3-yaml
|
|
61
|
+
- python3-pycparser
|
|
62
|
+
- python3-clang
|
|
63
|
+
- version: "2.1.2-violation-witnesses-v1"
|
|
64
|
+
doi: 10.5281/zenodo.17792484
|
|
65
|
+
benchexec_toolinfo_options: ["--witness", "${witness}", "--expectViolationWitness", "--ignoreSelfLoops", "--expectedWitnessVersion", "1"]
|
|
66
|
+
required_ubuntu_packages:
|
|
67
|
+
- python3-lxml
|
|
68
|
+
- python3-jsonschema
|
|
69
|
+
- python3-yaml
|
|
70
|
+
- python3-pycparser
|
|
71
|
+
- python3-clang
|
|
72
|
+
|
|
36
73
|
- version: "svcomp25-correctness-graphml"
|
|
37
74
|
doi: 10.5281/zenodo.15050742
|
|
38
75
|
benchexec_toolinfo_options: ["--witness", "${witness}", "--expectCorrectnessWitness", "--ignoreSelfLoops", "--expectedWitnessVersion", "1.0"]
|
|
@@ -41,6 +78,8 @@ versions:
|
|
|
41
78
|
- python3-jsonschema
|
|
42
79
|
- python3-yaml
|
|
43
80
|
- python3-pycparser
|
|
81
|
+
base_container_images:
|
|
82
|
+
- docker.io/ubuntu:24.04
|
|
44
83
|
- version: "svcomp25-violation-graphml"
|
|
45
84
|
doi: 10.5281/zenodo.15050742
|
|
46
85
|
benchexec_toolinfo_options: ["--witness", "${witness}", "--expectViolationWitness", "--ignoreSelfLoops", "--expectedWitnessVersion", "1.0"]
|
|
@@ -49,6 +88,8 @@ versions:
|
|
|
49
88
|
- python3-jsonschema
|
|
50
89
|
- python3-yaml
|
|
51
90
|
- python3-pycparser
|
|
91
|
+
base_container_images:
|
|
92
|
+
- docker.io/ubuntu:24.04
|
|
52
93
|
- version: "svcomp25-correctness-yaml"
|
|
53
94
|
doi: 10.5281/zenodo.15050742
|
|
54
95
|
benchexec_toolinfo_options: ["--witness", "${witness}", "--expectCorrectnessWitness", "--ignoreSelfLoops", "--expectedWitnessVersion", "2.0"]
|
|
@@ -57,6 +98,8 @@ versions:
|
|
|
57
98
|
- python3-jsonschema
|
|
58
99
|
- python3-yaml
|
|
59
100
|
- python3-pycparser
|
|
101
|
+
base_container_images:
|
|
102
|
+
- docker.io/ubuntu:24.04
|
|
60
103
|
- version: "svcomp25-violation-yaml"
|
|
61
104
|
doi: 10.5281/zenodo.15050742
|
|
62
105
|
benchexec_toolinfo_options: ["--witness", "${witness}", "--expectViolationWitness", "--ignoreSelfLoops", "--expectedWitnessVersion", "2.0"]
|
|
@@ -65,6 +108,9 @@ versions:
|
|
|
65
108
|
- python3-jsonschema
|
|
66
109
|
- python3-yaml
|
|
67
110
|
- python3-pycparser
|
|
111
|
+
base_container_images:
|
|
112
|
+
- docker.io/ubuntu:24.04
|
|
113
|
+
|
|
68
114
|
- version: "svcomp24-correctness-graphml"
|
|
69
115
|
doi: 10.5281/zenodo.10213801
|
|
70
116
|
benchexec_toolinfo_options: ["--expectCorrectnessWitness", "--ignoreSelfLoops", "--expectedWitnessVersion", "1.0"]
|
|
@@ -99,8 +145,65 @@ versions:
|
|
|
99
145
|
- python3-pycparser
|
|
100
146
|
|
|
101
147
|
competition_participations:
|
|
148
|
+
- competition: "SV-COMP 2026"
|
|
149
|
+
track: "Validation of Correctness Witnesses v2"
|
|
150
|
+
label:
|
|
151
|
+
- auxiliary
|
|
152
|
+
tool_version: "2.1.2-correctness-witnesses-v2"
|
|
153
|
+
jury_member:
|
|
154
|
+
orcid: 0000-0002-8172-3184
|
|
155
|
+
name: Marian Lingsch-Rosenfeld
|
|
156
|
+
institution: LMU Munich
|
|
157
|
+
country: Germany
|
|
158
|
+
url: https://www.sosy-lab.org/people/lingsch-rosenfeld/
|
|
159
|
+
participants:
|
|
160
|
+
- orcid: 0000-0002-8172-3184
|
|
161
|
+
name: Marian Lingsch-Rosenfeld
|
|
162
|
+
- competition: "SV-COMP 2026"
|
|
163
|
+
track: "Validation of Correctness Witnesses v1"
|
|
164
|
+
label:
|
|
165
|
+
- auxiliary
|
|
166
|
+
tool_version: "2.1.2-correctness-witnesses-v1"
|
|
167
|
+
jury_member:
|
|
168
|
+
orcid: 0000-0002-8172-3184
|
|
169
|
+
name: Marian Lingsch-Rosenfeld
|
|
170
|
+
institution: LMU Munich
|
|
171
|
+
country: Germany
|
|
172
|
+
url: https://www.sosy-lab.org/people/lingsch-rosenfeld/
|
|
173
|
+
participants:
|
|
174
|
+
- orcid: 0000-0002-8172-3184
|
|
175
|
+
name: Marian Lingsch-Rosenfeld
|
|
176
|
+
- competition: "SV-COMP 2026"
|
|
177
|
+
track: "Validation of Violation Witnesses v2"
|
|
178
|
+
label:
|
|
179
|
+
- auxiliary
|
|
180
|
+
tool_version: "2.1.2-violation-witnesses-v2"
|
|
181
|
+
jury_member:
|
|
182
|
+
orcid: 0000-0002-8172-3184
|
|
183
|
+
name: Marian Lingsch-Rosenfeld
|
|
184
|
+
institution: LMU Munich
|
|
185
|
+
country: Germany
|
|
186
|
+
url: https://www.sosy-lab.org/people/lingsch-rosenfeld/
|
|
187
|
+
participants:
|
|
188
|
+
- orcid: 0000-0002-8172-3184
|
|
189
|
+
name: Marian Lingsch-Rosenfeld
|
|
190
|
+
- competition: "SV-COMP 2026"
|
|
191
|
+
track: "Validation of Violation Witnesses v1"
|
|
192
|
+
label:
|
|
193
|
+
- auxiliary
|
|
194
|
+
tool_version: "2.1.2-violation-witnesses-v1"
|
|
195
|
+
jury_member:
|
|
196
|
+
orcid: 0000-0002-8172-3184
|
|
197
|
+
name: Marian Lingsch-Rosenfeld
|
|
198
|
+
institution: LMU Munich
|
|
199
|
+
country: Germany
|
|
200
|
+
url: https://www.sosy-lab.org/people/lingsch-rosenfeld/
|
|
201
|
+
participants:
|
|
202
|
+
- orcid: 0000-0002-8172-3184
|
|
203
|
+
name: Marian Lingsch-Rosenfeld
|
|
204
|
+
|
|
102
205
|
- competition: "SV-COMP 2025"
|
|
103
|
-
track: "Validation of Correctness Witnesses
|
|
206
|
+
track: "Validation of Correctness Witnesses v1"
|
|
104
207
|
label:
|
|
105
208
|
- auxiliary
|
|
106
209
|
tool_version: "svcomp25-correctness-graphml"
|
|
@@ -111,7 +214,7 @@ competition_participations:
|
|
|
111
214
|
country: Germany
|
|
112
215
|
url: https://www.sosy-lab.org/people/lingsch-rosenfeld/
|
|
113
216
|
- competition: "SV-COMP 2025"
|
|
114
|
-
track: "Validation of Correctness Witnesses
|
|
217
|
+
track: "Validation of Correctness Witnesses v2"
|
|
115
218
|
label:
|
|
116
219
|
- auxiliary
|
|
117
220
|
tool_version: "svcomp25-correctness-yaml"
|
|
@@ -122,7 +225,7 @@ competition_participations:
|
|
|
122
225
|
country: Germany
|
|
123
226
|
url: https://www.sosy-lab.org/people/lingsch-rosenfeld/
|
|
124
227
|
- competition: "SV-COMP 2025"
|
|
125
|
-
track: "Validation of Violation Witnesses
|
|
228
|
+
track: "Validation of Violation Witnesses v1"
|
|
126
229
|
label:
|
|
127
230
|
- auxiliary
|
|
128
231
|
tool_version: "svcomp25-violation-graphml"
|
|
@@ -133,7 +236,7 @@ competition_participations:
|
|
|
133
236
|
country: Germany
|
|
134
237
|
url: https://www.sosy-lab.org/people/lingsch-rosenfeld/
|
|
135
238
|
- competition: "SV-COMP 2025"
|
|
136
|
-
track: "Validation of Violation Witnesses
|
|
239
|
+
track: "Validation of Violation Witnesses v2"
|
|
137
240
|
label:
|
|
138
241
|
- auxiliary
|
|
139
242
|
tool_version: "svcomp25-violation-yaml"
|
|
@@ -144,7 +247,7 @@ competition_participations:
|
|
|
144
247
|
country: Germany
|
|
145
248
|
url: https://www.sosy-lab.org/people/lingsch-rosenfeld/
|
|
146
249
|
- competition: "SV-COMP 2024"
|
|
147
|
-
track: "Validation of Correctness Witnesses
|
|
250
|
+
track: "Validation of Correctness Witnesses v1"
|
|
148
251
|
label:
|
|
149
252
|
- auxiliary
|
|
150
253
|
tool_version: "svcomp24-correctness-graphml"
|
|
@@ -155,7 +258,7 @@ competition_participations:
|
|
|
155
258
|
country: Germany
|
|
156
259
|
url: https://www.sosy-lab.org/people/lingsch-rosenfeld/
|
|
157
260
|
- competition: "SV-COMP 2024"
|
|
158
|
-
track: "Validation of Correctness Witnesses
|
|
261
|
+
track: "Validation of Correctness Witnesses v2"
|
|
159
262
|
label:
|
|
160
263
|
- auxiliary
|
|
161
264
|
tool_version: "svcomp24-correctness-yaml"
|
|
@@ -166,7 +269,7 @@ competition_participations:
|
|
|
166
269
|
country: Germany
|
|
167
270
|
url: https://www.sosy-lab.org/people/lingsch-rosenfeld/
|
|
168
271
|
- competition: "SV-COMP 2024"
|
|
169
|
-
track: "Validation of Violation Witnesses
|
|
272
|
+
track: "Validation of Violation Witnesses v1"
|
|
170
273
|
label:
|
|
171
274
|
- auxiliary
|
|
172
275
|
tool_version: "svcomp24-violation-graphml"
|
|
@@ -177,7 +280,7 @@ competition_participations:
|
|
|
177
280
|
country: Germany
|
|
178
281
|
url: https://www.sosy-lab.org/people/lingsch-rosenfeld/
|
|
179
282
|
- competition: "SV-COMP 2024"
|
|
180
|
-
track: "Validation of Violation Witnesses
|
|
283
|
+
track: "Validation of Violation Witnesses v2"
|
|
181
284
|
label:
|
|
182
285
|
- auxiliary
|
|
183
286
|
tool_version: "svcomp24-violation-yaml"
|
|
@@ -33,13 +33,41 @@ maintainers:
|
|
|
33
33
|
url: https://www.sosy-lab.org/people/lingsch-rosenfeld/
|
|
34
34
|
|
|
35
35
|
versions:
|
|
36
|
+
- version: "0.0.2"
|
|
37
|
+
doi: 10.5281/zenodo.17769876
|
|
38
|
+
benchexec_toolinfo_options: []
|
|
39
|
+
required_ubuntu_packages:
|
|
40
|
+
- python3-yaml
|
|
41
|
+
base_container_images:
|
|
42
|
+
- docker.io/ubuntu:24.04
|
|
43
|
+
full_container_images:
|
|
44
|
+
- registry.gitlab.com/sosy-lab/benchmarking/competition-scripts/user:2026
|
|
36
45
|
- version: "svcomp25"
|
|
37
46
|
doi: 10.5281/zenodo.14205153
|
|
38
47
|
benchexec_toolinfo_options: []
|
|
39
48
|
required_ubuntu_packages:
|
|
40
49
|
- python3-yaml
|
|
41
|
-
|
|
50
|
+
base_container_images:
|
|
51
|
+
- docker.io/ubuntu:24.04
|
|
52
|
+
full_container_images:
|
|
53
|
+
- registry.gitlab.com/sosy-lab/benchmarking/competition-scripts/user:2026
|
|
42
54
|
competition_participations:
|
|
55
|
+
- competition: "SV-COMP 2026"
|
|
56
|
+
track: "Verification"
|
|
57
|
+
label:
|
|
58
|
+
- auxiliary
|
|
59
|
+
tool_version: "0.0.2"
|
|
60
|
+
jury_member:
|
|
61
|
+
orcid: 0000-0002-8172-3184
|
|
62
|
+
name: Marian Lingsch-Rosenfeld
|
|
63
|
+
institution: LMU Munich
|
|
64
|
+
country: Germany
|
|
65
|
+
url: https://www.sosy-lab.org/people/lingsch-rosenfeld/
|
|
66
|
+
participants:
|
|
67
|
+
- orcid: 0000-0003-4832-7662
|
|
68
|
+
name: Dirk Beyer
|
|
69
|
+
- orcid: 0000-0002-8172-3184
|
|
70
|
+
name: Marian Lingsch-Rosenfeld
|
|
43
71
|
- competition: "SV-COMP 2025"
|
|
44
72
|
track: "Verification"
|
|
45
73
|
label:
|
fm_weck/zenodo.py
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
# This file is part of fm-weck: executing fm-tools in containerized environments.
|
|
2
|
+
# https://gitlab.com/sosy-lab/software/fm-weck
|
|
3
|
+
#
|
|
4
|
+
# SPDX-FileCopyrightText: 2024 Dirk Beyer <https://www.sosy-lab.org>
|
|
5
|
+
#
|
|
6
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
import httpx
|
|
14
|
+
from dotenv import find_dotenv, load_dotenv, set_key
|
|
15
|
+
from tqdm import tqdm
|
|
16
|
+
|
|
17
|
+
from fm_weck.config import Config
|
|
18
|
+
from fm_weck.exceptions import ZenodoError
|
|
19
|
+
from fm_weck.image_mgr import ImageMgr
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class TokenAuth(httpx.Auth):
|
|
23
|
+
def __init__(self, token: str):
|
|
24
|
+
self.token = token
|
|
25
|
+
|
|
26
|
+
def auth_flow(self, request):
|
|
27
|
+
request.headers["Authorization"] = f"Bearer {self.token}"
|
|
28
|
+
yield request
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ZenodoSession:
|
|
32
|
+
def __init__(self, access_token: str | None = None, is_sandbox: bool = False):
|
|
33
|
+
if is_sandbox:
|
|
34
|
+
self.base_url = "https://sandbox.zenodo.org/api"
|
|
35
|
+
else:
|
|
36
|
+
self.base_url = "https://zenodo.org/api"
|
|
37
|
+
self.deposit_url = self.base_url + "/deposit/depositions"
|
|
38
|
+
self.download_url = self.base_url + "/records"
|
|
39
|
+
self.access_token = access_token
|
|
40
|
+
if self.access_token is not None:
|
|
41
|
+
self.client = httpx.Client(auth=TokenAuth(self.access_token))
|
|
42
|
+
else:
|
|
43
|
+
self.client = httpx.Client()
|
|
44
|
+
|
|
45
|
+
def _require_access_token(self):
|
|
46
|
+
if self.access_token is None:
|
|
47
|
+
raise ZenodoError("An API access token is required to interact with the Zenodo API.")
|
|
48
|
+
|
|
49
|
+
def publish(self, image_info: tuple):
|
|
50
|
+
"""
|
|
51
|
+
Publish an image on Zenodo.
|
|
52
|
+
"""
|
|
53
|
+
self._require_access_token()
|
|
54
|
+
|
|
55
|
+
deposition_id = self.upload(image_info)
|
|
56
|
+
publish_url = self.deposit_url + "/" + deposition_id + "/" + "actions/publish"
|
|
57
|
+
r = httpx.post(publish_url)
|
|
58
|
+
|
|
59
|
+
if r.status_code >= 400:
|
|
60
|
+
self.delete_deposition(deposition_id)
|
|
61
|
+
raise ZenodoError(f"Error trying to publish an image: {r.json()['message']}")
|
|
62
|
+
|
|
63
|
+
return r.json()["doi"]
|
|
64
|
+
|
|
65
|
+
def upload(self, image_info: tuple[str, str, Path]) -> str:
|
|
66
|
+
"""
|
|
67
|
+
Upload an image to Zenodo and leave it in draft mode (unpublished).
|
|
68
|
+
"""
|
|
69
|
+
self._require_access_token()
|
|
70
|
+
|
|
71
|
+
image_name = image_info[0]
|
|
72
|
+
image_version = image_info[1]
|
|
73
|
+
image_path = image_info[2]
|
|
74
|
+
# Upload the actual file
|
|
75
|
+
upload_name = image_name + "_" + image_version + ".tar"
|
|
76
|
+
upload_name = upload_name.replace("/", "-")
|
|
77
|
+
metadata = self.get_upload_metadata(upload_name, image_version)
|
|
78
|
+
record_id = self.check_existing_records(metadata)
|
|
79
|
+
logging.debug(f"Record ID: {record_id}")
|
|
80
|
+
# Check if we are adding a new version or creating a new record
|
|
81
|
+
if record_id:
|
|
82
|
+
r = self.client.post(self.deposit_url + "/" + record_id + "/actions/newversion")
|
|
83
|
+
else:
|
|
84
|
+
logging.debug(f"{metadata=}")
|
|
85
|
+
r = self.client.post(
|
|
86
|
+
self.deposit_url,
|
|
87
|
+
json=metadata,
|
|
88
|
+
headers={"Content-Type": "application/json", "Authorization": f"Bearer {self.access_token}"},
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
if r.status_code >= 400:
|
|
92
|
+
logging.debug(f"{r.status_code} - {r.text}")
|
|
93
|
+
raise ZenodoError(f"Error trying to upload the deposition metadata to Zenodo: {r.json()['message']}")
|
|
94
|
+
bucket_url = r.json()["links"]["bucket"]
|
|
95
|
+
deposition_id = str(r.json()["id"])
|
|
96
|
+
|
|
97
|
+
if record_id:
|
|
98
|
+
self.prepare_new_version_upload(metadata, deposition_id)
|
|
99
|
+
|
|
100
|
+
r = self.upload_with_progress_bar(image_path, upload_name, bucket_url)
|
|
101
|
+
if r.status_code >= 400:
|
|
102
|
+
self.delete_deposition(deposition_id)
|
|
103
|
+
raise ZenodoError(f"Error trying to upload image to Zenodo: {r.json()['message']}")
|
|
104
|
+
|
|
105
|
+
return deposition_id
|
|
106
|
+
|
|
107
|
+
def upload_with_progress_bar(self, image_path, upload_name, bucket_url):
|
|
108
|
+
file_size = os.path.getsize(image_path)
|
|
109
|
+
|
|
110
|
+
with (
|
|
111
|
+
open(image_path, "rb") as image,
|
|
112
|
+
tqdm(total=file_size, unit="B", unit_scale=True, desc="Uploading") as progress_bar,
|
|
113
|
+
):
|
|
114
|
+
|
|
115
|
+
def file_iterator():
|
|
116
|
+
chunk_size = 1024 * 1024 * 5 # 5 MB
|
|
117
|
+
while True:
|
|
118
|
+
chunk = image.read(chunk_size)
|
|
119
|
+
if not chunk:
|
|
120
|
+
break
|
|
121
|
+
yield chunk
|
|
122
|
+
progress_bar.update(len(chunk))
|
|
123
|
+
|
|
124
|
+
r = self.client.put(
|
|
125
|
+
f"{bucket_url}/{upload_name}",
|
|
126
|
+
data=file_iterator(),
|
|
127
|
+
headers={"Content-Length": str(file_size)},
|
|
128
|
+
timeout=300,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
return r
|
|
132
|
+
|
|
133
|
+
def download(self, doi: str, download_path: str | None, no_progress_bar: bool = False) -> Path:
|
|
134
|
+
"""
|
|
135
|
+
Download an image from Zenodo using its DOI.
|
|
136
|
+
"""
|
|
137
|
+
download_url = self.resolve_download_info(doi)
|
|
138
|
+
image_name = download_url.split("/")[-2] + ".tar"
|
|
139
|
+
if no_progress_bar:
|
|
140
|
+
r = self.client.get(download_url)
|
|
141
|
+
if r.status_code >= 400:
|
|
142
|
+
raise ZenodoError(f"Error trying to download image: {r.json()['message']}")
|
|
143
|
+
content = r.content
|
|
144
|
+
else:
|
|
145
|
+
content = self.download_with_progress_bar(download_url)
|
|
146
|
+
|
|
147
|
+
return self.save_image(image_name, content, download_path)
|
|
148
|
+
|
|
149
|
+
def download_with_progress_bar(self, url: str):
|
|
150
|
+
content = bytearray()
|
|
151
|
+
|
|
152
|
+
with self.client.stream("GET", url) as response:
|
|
153
|
+
response.raise_for_status()
|
|
154
|
+
|
|
155
|
+
total_size = int(response.headers.get("Content-Length", 0))
|
|
156
|
+
chunk_size = 1024 * 1024 * 5 # 5 MB
|
|
157
|
+
|
|
158
|
+
with tqdm(total=total_size, unit="B", unit_scale=True, desc="Downloading") as progress_bar:
|
|
159
|
+
for chunk in response.iter_bytes(chunk_size):
|
|
160
|
+
content.extend(chunk)
|
|
161
|
+
progress_bar.update(len(chunk))
|
|
162
|
+
|
|
163
|
+
return content
|
|
164
|
+
|
|
165
|
+
def resolve_download_info(self, doi: str) -> str:
|
|
166
|
+
"""
|
|
167
|
+
Extract the download URL from the deposition metadata.
|
|
168
|
+
|
|
169
|
+
:return: The download URL of the image.
|
|
170
|
+
"""
|
|
171
|
+
doi_id = doi.split(".")[-1]
|
|
172
|
+
record_url = self.download_url + "/" + doi_id
|
|
173
|
+
r = self.client.get(record_url)
|
|
174
|
+
if r.status_code >= 400:
|
|
175
|
+
raise ZenodoError(f"Error trying to access the image record: {r.json()['message']}")
|
|
176
|
+
|
|
177
|
+
file_name = r.json()["files"][0]["key"]
|
|
178
|
+
|
|
179
|
+
return record_url + "/files/" + file_name + "/content"
|
|
180
|
+
|
|
181
|
+
def save_image(self, image_name: str, image: bytes, save_path):
|
|
182
|
+
save_path = Path().cwd() / image_name if not save_path else Path().cwd() / Path(save_path) / image_name
|
|
183
|
+
save_path.parent.mkdir(parents=True, exist_ok=True)
|
|
184
|
+
save_path.write_bytes(image)
|
|
185
|
+
return save_path
|
|
186
|
+
|
|
187
|
+
def delete_deposition(self, deposition_id: str):
|
|
188
|
+
r = self.client.delete(self.deposit_url + "/" + deposition_id)
|
|
189
|
+
if r.status_code >= 400:
|
|
190
|
+
raise ZenodoError(f"Error trying to delete a deposition resource from Zenodo: {r.json()['message']}")
|
|
191
|
+
|
|
192
|
+
def check_existing_records(self, metadata: dict):
|
|
193
|
+
"""
|
|
194
|
+
Check if a record with the same name already exists in the depositions.
|
|
195
|
+
If it exists, prompt whether the image should be uploaded as a new version.
|
|
196
|
+
|
|
197
|
+
:return: The deposition ID in case of a positive prompt response, or an empty string.
|
|
198
|
+
"""
|
|
199
|
+
r = self.client.get(self.deposit_url)
|
|
200
|
+
if r.status_code >= 400:
|
|
201
|
+
raise ZenodoError(f"Error trying to access the image record: {r.json()['message']}")
|
|
202
|
+
|
|
203
|
+
for record in r.json():
|
|
204
|
+
if metadata["metadata"]["title"] == record["title"]:
|
|
205
|
+
while True:
|
|
206
|
+
response = (
|
|
207
|
+
input(
|
|
208
|
+
(
|
|
209
|
+
"The record already exists. Do you want to upload this as a new version "
|
|
210
|
+
"of the existing record?\n"
|
|
211
|
+
"(y)es: upload as a new version of an existing record\n"
|
|
212
|
+
"(n)o: create a new record\n"
|
|
213
|
+
"(a)bort: cancel the upload\n"
|
|
214
|
+
)
|
|
215
|
+
)
|
|
216
|
+
.strip()
|
|
217
|
+
.lower()
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
if response == "y":
|
|
221
|
+
return str(record["id"])
|
|
222
|
+
elif response == "n":
|
|
223
|
+
break
|
|
224
|
+
elif response == "a":
|
|
225
|
+
exit(0)
|
|
226
|
+
return ""
|
|
227
|
+
|
|
228
|
+
def prepare_new_version_upload(self, metadata: dict, deposition_id: str) -> None:
|
|
229
|
+
"""
|
|
230
|
+
Set up the metadata for the new version and delete the old versions' files from that version.
|
|
231
|
+
"""
|
|
232
|
+
r = self.client.put(
|
|
233
|
+
self.deposit_url + "/" + deposition_id,
|
|
234
|
+
data=json.dumps(metadata), # type: ignore
|
|
235
|
+
)
|
|
236
|
+
if r.status_code >= 400:
|
|
237
|
+
raise ZenodoError(f"Error trying to upload the new version metadata to Zenodo: {r.json()['message']}")
|
|
238
|
+
|
|
239
|
+
for file in r.json()["files"]:
|
|
240
|
+
r = self.client.delete(
|
|
241
|
+
self.deposit_url + "/" + deposition_id + "/files/" + file["id"],
|
|
242
|
+
)
|
|
243
|
+
if r.status_code >= 400:
|
|
244
|
+
raise ZenodoError(
|
|
245
|
+
f"Error trying to delete old version files from the new version: {r.json()['message']}"
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
def get_upload_metadata(self, image_name: str, image_version: str) -> dict:
|
|
249
|
+
description = (
|
|
250
|
+
f"This is an image file.\n"
|
|
251
|
+
f"<p>In case of manual download, the image can be imported locally using the command: "
|
|
252
|
+
f"<code>[docker|podman] load --input {image_name}</code></p>"
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
upload_metadata = {
|
|
256
|
+
"metadata": {
|
|
257
|
+
"title": image_name,
|
|
258
|
+
"version": image_version,
|
|
259
|
+
"upload_type": "software",
|
|
260
|
+
"creators": [{"name": "fm-weck"}],
|
|
261
|
+
"publication_date": datetime.now().date().isoformat(),
|
|
262
|
+
"access_right": "open",
|
|
263
|
+
"license": "Apache-2.0",
|
|
264
|
+
"description": description,
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return upload_metadata
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
class ZenodoMgr(object):
|
|
271
|
+
"""
|
|
272
|
+
Zenodo API manager.
|
|
273
|
+
|
|
274
|
+
Args:
|
|
275
|
+
engine (object): The fm-weck engine object.
|
|
276
|
+
access_token (str, optional): The access token for authentication with the API. Defaults to None.
|
|
277
|
+
image (str, optional): The image to be published. Required if no doi is given. Defaults to None.
|
|
278
|
+
doi (str, optional): The DOI of the image to be pulled. Required if no image is given. Defaults to None.
|
|
279
|
+
|
|
280
|
+
Raises:
|
|
281
|
+
ValueError: If a chosen image is invalid or does not exist.
|
|
282
|
+
"""
|
|
283
|
+
|
|
284
|
+
def __init__(
|
|
285
|
+
self,
|
|
286
|
+
engine,
|
|
287
|
+
access_token: str | None,
|
|
288
|
+
image: str | None,
|
|
289
|
+
doi: str | None,
|
|
290
|
+
is_sandbox: bool = False,
|
|
291
|
+
config: Config | None = None,
|
|
292
|
+
):
|
|
293
|
+
access_token = access_token or ZenodoMgr.load_zenodo_token(config)
|
|
294
|
+
|
|
295
|
+
self.session = ZenodoSession(access_token, is_sandbox)
|
|
296
|
+
self.image = image
|
|
297
|
+
self.engine = engine
|
|
298
|
+
if image:
|
|
299
|
+
self.image_info = ImageMgr().prepare_image_for_zenodo(engine, image)
|
|
300
|
+
self.doi = doi
|
|
301
|
+
|
|
302
|
+
def publish(self):
|
|
303
|
+
"""
|
|
304
|
+
Publish an image on Zenodo.
|
|
305
|
+
|
|
306
|
+
Raises:
|
|
307
|
+
ZenodoError: If Zenodo API returns an error.
|
|
308
|
+
"""
|
|
309
|
+
if self.image is None:
|
|
310
|
+
raise ValueError("No image specified for publishing.")
|
|
311
|
+
print("Publishing image to Zenodo...")
|
|
312
|
+
doi = self.session.publish(self.image_info)
|
|
313
|
+
print("Image successfully published.")
|
|
314
|
+
ImageMgr().tag_image(self.engine, self.image, doi)
|
|
315
|
+
|
|
316
|
+
def push(self):
|
|
317
|
+
"""
|
|
318
|
+
Upload an image to Zenodo and leave it in draft mode (unpublished).
|
|
319
|
+
|
|
320
|
+
Raises:
|
|
321
|
+
ZenodoError: If Zenodo API returns an error.
|
|
322
|
+
"""
|
|
323
|
+
if self.image is None:
|
|
324
|
+
raise ValueError("No image specified for pushing.")
|
|
325
|
+
logging.info(f"Uploading image to Zenodo... {self.image_info}")
|
|
326
|
+
self.session.upload(self.image_info)
|
|
327
|
+
print("Image successfully uploaded.")
|
|
328
|
+
|
|
329
|
+
def pull(self, download_path: str | None, force_pull: bool = False):
|
|
330
|
+
"""
|
|
331
|
+
Download an image from Zenodo using its DOI.
|
|
332
|
+
|
|
333
|
+
Raises:
|
|
334
|
+
ValueError: If no DOI is provided.
|
|
335
|
+
ZenodoError: If Zenodo API returns an error.
|
|
336
|
+
"""
|
|
337
|
+
if self.doi:
|
|
338
|
+
doi = ImageMgr().check_if_doi_exists_locally(self.engine, self.doi)
|
|
339
|
+
else:
|
|
340
|
+
raise ValueError("No DOI provided.")
|
|
341
|
+
|
|
342
|
+
if not force_pull and not doi:
|
|
343
|
+
print("Image already exists locally. Pulling aborted.")
|
|
344
|
+
return
|
|
345
|
+
|
|
346
|
+
print("Pulling image from Zenodo...")
|
|
347
|
+
tar_path = self.session.download(self.doi, download_path)
|
|
348
|
+
ImageMgr().load_image(self.engine, tar_path, self.doi)
|
|
349
|
+
print("Image successfully pulled.")
|
|
350
|
+
tar_path.unlink()
|
|
351
|
+
|
|
352
|
+
@staticmethod
|
|
353
|
+
def save_zenodo_token(config, token):
|
|
354
|
+
token_path = find_dotenv(filename=".zenodo_token")
|
|
355
|
+
token_path = Path.cwd() / config.get("defaults").get("token_location") if not token_path else Path(token_path)
|
|
356
|
+
|
|
357
|
+
print(f"This action will overwrite the token at: {token_path}\nDo you wish to continue? [y/N]")
|
|
358
|
+
if input().lower() == "y":
|
|
359
|
+
load_dotenv(token_path)
|
|
360
|
+
set_key(token_path, "ZENODO_TOKEN", token)
|
|
361
|
+
print(f"Token saved to: {token_path}")
|
|
362
|
+
else:
|
|
363
|
+
print("Action aborted.")
|
|
364
|
+
|
|
365
|
+
@staticmethod
|
|
366
|
+
def load_zenodo_token(config):
|
|
367
|
+
"""
|
|
368
|
+
Load the Zenodo token from the Zenodo config file.
|
|
369
|
+
|
|
370
|
+
Raises:
|
|
371
|
+
ZenodoError: If no Zenodo token is found.
|
|
372
|
+
"""
|
|
373
|
+
token_path = find_dotenv(filename=".zenodo_token")
|
|
374
|
+
token_path = Path.cwd() / config.get("defaults").get("token_location") if not token_path else Path(token_path)
|
|
375
|
+
load_dotenv(Path(token_path))
|
|
376
|
+
token = os.getenv("ZENODO_TOKEN")
|
|
377
|
+
|
|
378
|
+
if token:
|
|
379
|
+
print(f"Using token from: {token_path}\n")
|
|
380
|
+
|
|
381
|
+
return token
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fm-weck
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.6.0
|
|
4
4
|
Author-email: Henrik Wachowitz <henrik.wachowitz@ifi.lmu.de>
|
|
5
5
|
Maintainer-email: Henrik Wachowitz <henrik.wachowitz@ifi.lmu.de>
|
|
6
6
|
Classifier: Development Status :: 4 - Beta
|
|
@@ -11,6 +11,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.12
|
|
12
12
|
Requires-Python: >=3.10
|
|
13
13
|
Requires-Dist: argcomplete
|
|
14
|
+
Requires-Dist: dotenv
|
|
14
15
|
Requires-Dist: fm-tools>=1.0.0
|
|
15
16
|
Requires-Dist: pyyaml>=6.0
|
|
16
17
|
Requires-Dist: tabulate
|