fairscape-cli 1.2.1__tar.gz → 1.2.2__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.
- {fairscape_cli-1.2.1/src/fairscape_cli.egg-info → fairscape_cli-1.2.2}/PKG-INFO +1 -1
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/pyproject.toml +1 -1
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/commands/build_commands.py +3 -2
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/data_fetcher/GenomicData.py +3 -3
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/datasheet_builder/rocrate/datasheet_generator.py +38 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/datasheet_builder/rocrate/summary_generator.py +37 -3
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/datasheet_builder/templates/base.html +70 -70
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/datasheet_builder/templates/sections/subcrates.html +2 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/datasheet_builder/templates/sections/summary.html +1 -1
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/entailments/find_outputs.py +3 -2
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/entailments/inverse.py +3 -2
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/bagit.py +2 -1
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/rocrate.py +15 -14
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/schema/tabular.py +2 -2
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/tracking/provenance_tracker.py +3 -2
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/utils/build_utils.py +30 -5
- fairscape_cli-1.2.2/src/fairscape_cli/utils/serialization.py +31 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2/src/fairscape_cli.egg-info}/PKG-INFO +1 -1
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli.egg-info/SOURCES.txt +2 -1
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/LICENSE +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/MANIFEST.in +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/README.md +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/setup.cfg +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/__init__.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/__main__.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/commands/__init__.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/commands/augment_commands.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/commands/import_commands.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/commands/publish_commands.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/commands/rocrate_commands.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/commands/schema_commands.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/commands/track.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/config.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/data_fetcher/PhysioNetImporter.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/data_fetcher/__init__.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/data_fetcher/bioproject_fetcher.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/data_fetcher/cell_line_api.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/data_fetcher/generic_data/__init__.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/data_fetcher/generic_data/connectors/__init__.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/data_fetcher/generic_data/connectors/dataverse_connector.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/data_fetcher/generic_data/connectors/figshare_connector.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/data_fetcher/generic_data/research_data.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/datasheet_builder/__init__.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/datasheet_builder/evidence_graph/__init__.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/datasheet_builder/evidence_graph/graph_builder.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/datasheet_builder/evidence_graph/html_builder.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/datasheet_builder/generate_datasheet.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/datasheet_builder/rocrate/__init__.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/datasheet_builder/rocrate/section_generators.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/datasheet_builder/rocrate/utilities/__init__.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/datasheet_builder/templates/.DS_Store +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/datasheet_builder/templates/preview.html +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/datasheet_builder/templates/sections/distribution.html +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/datasheet_builder/templates/sections/overview.html +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/datasheet_builder/templates/sections/use_cases.html +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/entailments/__init__.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/entailments/evi.xml +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/jupyter/__init__.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/jupyter/magic.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/MLModel.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/__init__.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/base.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/biochem_entity.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/computation.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/dataset.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/experiment.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/guid_utils.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/instrument.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/pep.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/sample.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/schema/__init__.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/schema/default_schemas/__init__.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/schema/default_schemas/cm4ai_schemas/cm4ai_schemas.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/schema/default_schemas/cm4ai_schemas/v0.1.0/cm4ai_schema_apms_embedding.json +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/schema/default_schemas/cm4ai_schemas/v0.1.0/cm4ai_schema_apmsloader_ppi_edgelist.json +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/schema/default_schemas/cm4ai_schemas/v0.1.0/cm4ai_schema_apmsloader_ppi_gene_node_attributes.json +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/schema/default_schemas/cm4ai_schemas/v0.1.0/cm4ai_schema_coembedding.json +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/schema/default_schemas/cm4ai_schemas/v0.1.0/cm4ai_schema_hierarchy_cx.json +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/schema/default_schemas/cm4ai_schemas/v0.1.0/cm4ai_schema_hierarchy_ppi_cutoff.json +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/schema/default_schemas/cm4ai_schemas/v0.1.0/cm4ai_schema_image_embedding_emd.json +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/schema/default_schemas/cm4ai_schemas/v0.1.0/cm4ai_schema_image_embedding_labels_prob.json +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/schema/default_schemas/cm4ai_schemas/v0.1.0/cm4ai_schema_imageloader_gene_node_attributes.json +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/schema/default_schemas/cm4ai_schemas/v0.1.0/cm4ai_schema_imageloader_samplescopy.json +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/schema/default_schemas/cm4ai_schemas/v0.1.0/cm4ai_schema_imageloader_uniquecopy.json +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/schema/default_schemas/default.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/schema/utils.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/software.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/models/utils.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/publish/__init__.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/publish/publish_tools.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/tracking/__init__.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/tracking/config.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/tracking/io_capture.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/tracking/metadata_generator.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/tracking/utils.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/utils/huggingface_utils.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/utils/merkle.py +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli.egg-info/dependency_links.txt +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli.egg-info/entry_points.txt +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli.egg-info/requires.txt +0 -0
- {fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fairscape-cli
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.2
|
|
4
4
|
Summary: A utility for packaging objects and validating metadata for FAIRSCAPE
|
|
5
5
|
Author-email: Max Levinson <mal8ch@virginia.edu>, Justin Niestroy <jniestroy@gmail.com>, Sadnan Al Manir <sadnanalmanir@gmail.com>, Tim Clark <twc8q@virginia.edu>
|
|
6
6
|
License: Copyright 2023 THE RECTOR AND VISITORS OF THE UNIVERSITY OF VIRGINIA
|
|
@@ -29,6 +29,7 @@ from fairscape_cli.models import (
|
|
|
29
29
|
)
|
|
30
30
|
|
|
31
31
|
from fairscape_models.rocrate import ROCrateV1_2, ROCrateMetadataElem
|
|
32
|
+
from fairscape_cli.utils.serialization import prune_none
|
|
32
33
|
from fairscape_models.conversion.converter import ROCToTargetConverter
|
|
33
34
|
from fairscape_models.conversion.mapping.croissant import MAPPING_CONFIGURATION as CROISSANT_MAPPING
|
|
34
35
|
|
|
@@ -535,7 +536,7 @@ def generate_evidence_graph(
|
|
|
535
536
|
|
|
536
537
|
# Write the updated metadata back to the file
|
|
537
538
|
with open(metadata_file, 'w') as f:
|
|
538
|
-
json.dump(metadata, f, indent=2)
|
|
539
|
+
json.dump(prune_none(metadata), f, indent=2)
|
|
539
540
|
|
|
540
541
|
click.echo(f"Added hasEvidenceGraph reference to {ark_id} in RO-Crate metadata")
|
|
541
542
|
except Exception as e:
|
|
@@ -774,4 +775,4 @@ def validate_merkle_command(ctx, rocrate_path: pathlib.Path, release: bool):
|
|
|
774
775
|
elif s != c:
|
|
775
776
|
click.echo(f" CHANGED: {url}")
|
|
776
777
|
|
|
777
|
-
ctx.exit(1)
|
|
778
|
+
ctx.exit(1)
|
|
@@ -103,6 +103,7 @@ from fairscape_cli.data_fetcher.cell_line_api import get_cell_line_entity
|
|
|
103
103
|
from fairscape_cli.data_fetcher.bioproject_fetcher import fetch_bioproject_data
|
|
104
104
|
|
|
105
105
|
from fairscape_cli.models.rocrate import GenerateROCrate, AppendCrate
|
|
106
|
+
from fairscape_cli.utils.serialization import prune_none
|
|
106
107
|
from fairscape_cli.models.dataset import GenerateDataset
|
|
107
108
|
from fairscape_cli.models.experiment import GenerateExperiment
|
|
108
109
|
from fairscape_cli.models.instrument import GenerateInstrument
|
|
@@ -342,7 +343,7 @@ class GenomicData(BaseModel):
|
|
|
342
343
|
root_dataset_node["hasPart"].append({"@id": entity_id})
|
|
343
344
|
|
|
344
345
|
f.seek(0)
|
|
345
|
-
json.dump(crate_json, f, indent=2)
|
|
346
|
+
json.dump(prune_none(crate_json), f, indent=2)
|
|
346
347
|
f.truncate()
|
|
347
348
|
|
|
348
349
|
|
|
@@ -360,7 +361,7 @@ class GenomicData(BaseModel):
|
|
|
360
361
|
|
|
361
362
|
if updated:
|
|
362
363
|
f.seek(0)
|
|
363
|
-
json.dump(crate_json_final, f, indent=2)
|
|
364
|
+
json.dump(prune_none(crate_json_final), f, indent=2)
|
|
364
365
|
f.truncate()
|
|
365
366
|
|
|
366
367
|
|
|
@@ -500,4 +501,3 @@ class GenomicData(BaseModel):
|
|
|
500
501
|
experiments=Experiments(items=internal_experiments),
|
|
501
502
|
outputs=Outputs(items=outputs_list)
|
|
502
503
|
)
|
|
503
|
-
|
|
@@ -159,6 +159,8 @@ class DatasheetGenerator:
|
|
|
159
159
|
distribution = distribution_converter.convert()
|
|
160
160
|
|
|
161
161
|
subcrate_items = self._process_all_subcrates()
|
|
162
|
+
if not subcrate_items:
|
|
163
|
+
subcrate_items = self._build_single_crate_composition()
|
|
162
164
|
composition = CompositionSection(items=subcrate_items) if subcrate_items else None
|
|
163
165
|
|
|
164
166
|
return FairscapeDatasheet(
|
|
@@ -226,6 +228,42 @@ class DatasheetGenerator:
|
|
|
226
228
|
|
|
227
229
|
return subcrate_items
|
|
228
230
|
|
|
231
|
+
def _build_single_crate_composition(self) -> List[SubCrateItem]:
|
|
232
|
+
"""When no subcrates exist, treat the main crate itself as a single subcrate."""
|
|
233
|
+
try:
|
|
234
|
+
converter = ROCToTargetConverter(
|
|
235
|
+
source_crate=self.main_crate,
|
|
236
|
+
mapping_configuration=SUBCRATE_MAPPING_CONFIGURATION,
|
|
237
|
+
global_index=self.global_metadata_index
|
|
238
|
+
)
|
|
239
|
+
subcrate_item = converter.convert()
|
|
240
|
+
subcrate_item.published = self.published
|
|
241
|
+
subcrate_item.preview_url = ""
|
|
242
|
+
|
|
243
|
+
if not subcrate_item.size and self.base_dir.exists():
|
|
244
|
+
try:
|
|
245
|
+
dir_size = get_directory_size(str(self.base_dir))
|
|
246
|
+
subcrate_item.size = format_size(dir_size)
|
|
247
|
+
except Exception:
|
|
248
|
+
subcrate_item.size = "Unknown"
|
|
249
|
+
|
|
250
|
+
main_root = self.main_crate.metadataGraph[1].model_dump()
|
|
251
|
+
if not subcrate_item.doi:
|
|
252
|
+
subcrate_item.doi = main_root.get('identifier')
|
|
253
|
+
if not subcrate_item.related_publications:
|
|
254
|
+
pubs = main_root.get('associatedPublication', [])
|
|
255
|
+
if pubs:
|
|
256
|
+
subcrate_item.related_publications = pubs if isinstance(pubs, list) else [pubs]
|
|
257
|
+
|
|
258
|
+
self._enhance_subcrate_item(subcrate_item, self.main_crate)
|
|
259
|
+
|
|
260
|
+
return [subcrate_item]
|
|
261
|
+
except Exception as e:
|
|
262
|
+
print(f"Error building single crate composition: {e}")
|
|
263
|
+
import traceback
|
|
264
|
+
traceback.print_exc()
|
|
265
|
+
return []
|
|
266
|
+
|
|
229
267
|
def _enhance_subcrate_item(self, subcrate_item: SubCrateItem, subcrate: ROCrateV1_2):
|
|
230
268
|
"""Add statistical summary info to subcrate item if present."""
|
|
231
269
|
root_dict = subcrate.metadataGraph[1].model_dump() if len(subcrate.metadataGraph) > 1 else {}
|
|
@@ -11,6 +11,7 @@ from jinja2 import Environment
|
|
|
11
11
|
from fairscape_models.rocrate import ROCrateV1_2
|
|
12
12
|
from fairscape_models.conversion.mapping.AIReady import score_rocrate
|
|
13
13
|
from fairscape_models.conversion.models.AIReady import AIReadyScore
|
|
14
|
+
from fairscape_cli.utils.serialization import model_dump_pruned
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
@dataclass
|
|
@@ -89,7 +90,7 @@ class SummarySectionGenerator:
|
|
|
89
90
|
formats = []
|
|
90
91
|
formats = [f for f in formats if f and f != "unknown"]
|
|
91
92
|
|
|
92
|
-
|
|
93
|
+
summary = SummaryData(
|
|
93
94
|
name=root_data.get("name", "Unnamed Dataset"),
|
|
94
95
|
description=root_data.get("description", ""),
|
|
95
96
|
total_size_formatted=size_str,
|
|
@@ -100,6 +101,36 @@ class SummarySectionGenerator:
|
|
|
100
101
|
formats=formats
|
|
101
102
|
)
|
|
102
103
|
|
|
104
|
+
# Fallback: compute from graph if evi:* fields are absent (single crate case)
|
|
105
|
+
if summary.total_entities == 0:
|
|
106
|
+
formats_set = set()
|
|
107
|
+
for item in crate.metadataGraph:
|
|
108
|
+
if item.guid == "ro-crate-metadata.json":
|
|
109
|
+
continue
|
|
110
|
+
item_dict = item.model_dump(by_alias=True)
|
|
111
|
+
item_type = item_dict.get("@type", "")
|
|
112
|
+
type_str = " ".join(item_type) if isinstance(item_type, list) else str(item_type)
|
|
113
|
+
|
|
114
|
+
if "ROCrate" in type_str or "CreativeWork" in type_str:
|
|
115
|
+
continue
|
|
116
|
+
|
|
117
|
+
summary.total_entities += 1
|
|
118
|
+
|
|
119
|
+
if "Dataset" in type_str:
|
|
120
|
+
summary.dataset_count += 1
|
|
121
|
+
fmt = item_dict.get("fileFormat")
|
|
122
|
+
if fmt and fmt != "unknown":
|
|
123
|
+
formats_set.add(fmt)
|
|
124
|
+
elif "Software" in type_str or "SoftwareSourceCode" in type_str:
|
|
125
|
+
summary.software_count += 1
|
|
126
|
+
elif "Computation" in type_str:
|
|
127
|
+
summary.computation_count += 1
|
|
128
|
+
|
|
129
|
+
if not summary.formats and formats_set:
|
|
130
|
+
summary.formats = sorted(formats_set)
|
|
131
|
+
|
|
132
|
+
return summary
|
|
133
|
+
|
|
103
134
|
@staticmethod
|
|
104
135
|
def _format_size(size_bytes: int) -> str:
|
|
105
136
|
"""Format bytes to human-readable size."""
|
|
@@ -160,7 +191,7 @@ class SummarySectionGenerator:
|
|
|
160
191
|
|
|
161
192
|
def save_aiready_score(self, raw_score: AIReadyScore, output_path: Path) -> None:
|
|
162
193
|
"""Save the AI-Ready score to a JSON file."""
|
|
163
|
-
score_dict = raw_score
|
|
194
|
+
score_dict = model_dump_pruned(raw_score)
|
|
164
195
|
with open(output_path, 'w') as f:
|
|
165
196
|
json.dump(score_dict, f, indent=2)
|
|
166
197
|
|
|
@@ -183,8 +214,10 @@ class SummarySectionGenerator:
|
|
|
183
214
|
self.save_aiready_score(raw_score, aiready_json_path)
|
|
184
215
|
|
|
185
216
|
desc = summary.description
|
|
217
|
+
description_truncated = False
|
|
186
218
|
if len(desc) > 500:
|
|
187
|
-
desc = desc[:500].rsplit(" ", 1)[0]
|
|
219
|
+
desc = desc[:500].rsplit(" ", 1)[0]
|
|
220
|
+
description_truncated = True
|
|
188
221
|
|
|
189
222
|
formats_str = ", ".join(sorted(summary.formats)[:10])
|
|
190
223
|
if len(summary.formats) > 10:
|
|
@@ -192,6 +225,7 @@ class SummarySectionGenerator:
|
|
|
192
225
|
|
|
193
226
|
context = {
|
|
194
227
|
'description': desc,
|
|
228
|
+
'description_truncated': description_truncated,
|
|
195
229
|
'total_size': summary.total_size_formatted,
|
|
196
230
|
'total_entities': f"{summary.total_entities:,}" if summary.total_entities else "N/A",
|
|
197
231
|
'formats': formats_str,
|
{fairscape_cli-1.2.1 → fairscape_cli-1.2.2}/src/fairscape_cli/datasheet_builder/templates/base.html
RENAMED
|
@@ -18,18 +18,18 @@
|
|
|
18
18
|
.container {
|
|
19
19
|
max-width: 1100px;
|
|
20
20
|
margin: 0 auto;
|
|
21
|
-
padding:
|
|
21
|
+
padding: 12px;
|
|
22
22
|
background-color: white;
|
|
23
23
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
|
24
24
|
/* Added for smooth transition */
|
|
25
25
|
transition: margin-left 0.3s ease-in-out;
|
|
26
26
|
}
|
|
27
27
|
header {
|
|
28
|
-
margin-bottom:
|
|
28
|
+
margin-bottom: 10px;
|
|
29
29
|
border-bottom: 2px solid #2c3e50;
|
|
30
|
-
padding-bottom:
|
|
30
|
+
padding-bottom: 8px;
|
|
31
31
|
background-color: #f8f9fa;
|
|
32
|
-
padding:
|
|
32
|
+
padding: 12px;
|
|
33
33
|
border-radius: 5px;
|
|
34
34
|
}
|
|
35
35
|
h1 {
|
|
@@ -40,35 +40,35 @@
|
|
|
40
40
|
h2 {
|
|
41
41
|
font-size: 20px;
|
|
42
42
|
color: #2c3e50;
|
|
43
|
-
margin-top:
|
|
44
|
-
margin-bottom:
|
|
43
|
+
margin-top: 12px;
|
|
44
|
+
margin-bottom: 8px;
|
|
45
45
|
border-bottom: 1px solid #ddd;
|
|
46
|
-
padding-bottom:
|
|
46
|
+
padding-bottom: 4px;
|
|
47
47
|
}
|
|
48
48
|
h3 {
|
|
49
49
|
font-size: 18px;
|
|
50
50
|
color: #2c3e50;
|
|
51
|
-
margin-top:
|
|
52
|
-
margin-bottom:
|
|
51
|
+
margin-top: 10px;
|
|
52
|
+
margin-bottom: 6px;
|
|
53
53
|
}
|
|
54
54
|
h4 {
|
|
55
55
|
font-size: 16px;
|
|
56
56
|
color: #2c3e50;
|
|
57
|
-
margin-top:
|
|
58
|
-
margin-bottom:
|
|
57
|
+
margin-top: 8px;
|
|
58
|
+
margin-bottom: 6px;
|
|
59
59
|
}
|
|
60
60
|
.summary-section {
|
|
61
61
|
background-color: #f8f9fa;
|
|
62
|
-
padding:
|
|
62
|
+
padding: 12px;
|
|
63
63
|
border-radius: 4px;
|
|
64
|
-
margin-bottom:
|
|
64
|
+
margin-bottom: 15px;
|
|
65
65
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
|
|
66
66
|
}
|
|
67
67
|
.summary-row {
|
|
68
68
|
display: flex;
|
|
69
|
-
margin-bottom:
|
|
69
|
+
margin-bottom: 6px;
|
|
70
70
|
border-bottom: 1px solid #eee;
|
|
71
|
-
padding-bottom:
|
|
71
|
+
padding-bottom: 4px;
|
|
72
72
|
}
|
|
73
73
|
.summary-label {
|
|
74
74
|
width: 230px;
|
|
@@ -79,24 +79,24 @@
|
|
|
79
79
|
flex: 1;
|
|
80
80
|
}
|
|
81
81
|
.section-header {
|
|
82
|
-
margin:
|
|
83
|
-
padding-bottom:
|
|
82
|
+
margin: 12px 0 8px 0;
|
|
83
|
+
padding-bottom: 4px;
|
|
84
84
|
border-bottom: 1px solid #ddd;
|
|
85
85
|
color: #2c3e50;
|
|
86
86
|
}
|
|
87
87
|
.composition-summary {
|
|
88
88
|
display: flex;
|
|
89
89
|
flex-wrap: wrap;
|
|
90
|
-
gap:
|
|
91
|
-
margin-bottom:
|
|
90
|
+
gap: 10px;
|
|
91
|
+
margin-bottom: 10px;
|
|
92
92
|
}
|
|
93
93
|
.composition-item {
|
|
94
94
|
background-color: #f8f9fa;
|
|
95
95
|
border-radius: 4px;
|
|
96
|
-
padding:
|
|
96
|
+
padding: 10px;
|
|
97
97
|
text-align: center;
|
|
98
98
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
|
|
99
|
-
width: calc(20% -
|
|
99
|
+
width: calc(20% - 10px);
|
|
100
100
|
min-width: 120px;
|
|
101
101
|
}
|
|
102
102
|
.composition-count {
|
|
@@ -110,20 +110,20 @@
|
|
|
110
110
|
color: #555;
|
|
111
111
|
}
|
|
112
112
|
.subcrates-container {
|
|
113
|
-
margin-top:
|
|
113
|
+
margin-top: 15px;
|
|
114
114
|
}
|
|
115
115
|
.subcrate-summary {
|
|
116
116
|
background-color: #f8f9fa;
|
|
117
117
|
border-radius: 4px;
|
|
118
|
-
padding:
|
|
119
|
-
margin-bottom:
|
|
118
|
+
padding: 12px;
|
|
119
|
+
margin-bottom: 10px;
|
|
120
120
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
|
|
121
121
|
}
|
|
122
122
|
.subcrate-metadata {
|
|
123
|
-
margin-bottom:
|
|
123
|
+
margin-bottom: 8px;
|
|
124
124
|
}
|
|
125
125
|
.metadata-item {
|
|
126
|
-
margin-bottom:
|
|
126
|
+
margin-bottom: 3px;
|
|
127
127
|
}
|
|
128
128
|
.metadata-label {
|
|
129
129
|
font-weight: bold;
|
|
@@ -133,24 +133,24 @@
|
|
|
133
133
|
.section-summary {
|
|
134
134
|
background-color: white;
|
|
135
135
|
border-radius: 4px;
|
|
136
|
-
padding:
|
|
137
|
-
margin-top:
|
|
138
|
-
margin-bottom:
|
|
136
|
+
padding: 10px;
|
|
137
|
+
margin-top: 8px;
|
|
138
|
+
margin-bottom: 8px;
|
|
139
139
|
border: 1px solid #eaeaea;
|
|
140
140
|
}
|
|
141
141
|
.summary-grid {
|
|
142
142
|
display: grid;
|
|
143
143
|
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
|
144
|
-
gap:
|
|
144
|
+
gap: 10px;
|
|
145
145
|
}
|
|
146
146
|
.summary-item {
|
|
147
|
-
margin-bottom:
|
|
147
|
+
margin-bottom: 8px;
|
|
148
148
|
}
|
|
149
149
|
details {
|
|
150
|
-
margin-top:
|
|
150
|
+
margin-top: 8px;
|
|
151
151
|
border: 1px solid #eaeaea;
|
|
152
152
|
border-radius: 4px;
|
|
153
|
-
padding:
|
|
153
|
+
padding: 8px;
|
|
154
154
|
}
|
|
155
155
|
summary {
|
|
156
156
|
font-weight: bold;
|
|
@@ -175,7 +175,7 @@
|
|
|
175
175
|
color: #2c3e50;
|
|
176
176
|
}
|
|
177
177
|
.view-full-link {
|
|
178
|
-
margin-top:
|
|
178
|
+
margin-top: 8px;
|
|
179
179
|
text-align: right;
|
|
180
180
|
}
|
|
181
181
|
.view-full-link a {
|
|
@@ -194,15 +194,15 @@
|
|
|
194
194
|
.use-cases-section {
|
|
195
195
|
background-color: #f8f9fa;
|
|
196
196
|
border-radius: 4px;
|
|
197
|
-
padding:
|
|
198
|
-
margin-bottom:
|
|
197
|
+
padding: 12px;
|
|
198
|
+
margin-bottom: 15px;
|
|
199
199
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
|
|
200
200
|
}
|
|
201
201
|
.distribution-item,
|
|
202
202
|
.use-cases-item {
|
|
203
|
-
margin-bottom:
|
|
203
|
+
margin-bottom: 6px;
|
|
204
204
|
border-bottom: 1px solid #eee;
|
|
205
|
-
padding-bottom:
|
|
205
|
+
padding-bottom: 4px;
|
|
206
206
|
}
|
|
207
207
|
.distribution-label,
|
|
208
208
|
.use-cases-label {
|
|
@@ -226,16 +226,16 @@
|
|
|
226
226
|
}
|
|
227
227
|
.subcrate-title {
|
|
228
228
|
border-bottom: 2px solid #2c3e50;
|
|
229
|
-
padding-bottom:
|
|
230
|
-
margin-bottom:
|
|
229
|
+
padding-bottom: 6px;
|
|
230
|
+
margin-bottom: 8px;
|
|
231
231
|
color: #2c3e50;
|
|
232
232
|
}
|
|
233
233
|
.composition-grid {
|
|
234
234
|
display: grid;
|
|
235
235
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
236
|
-
gap:
|
|
237
|
-
margin-top:
|
|
238
|
-
margin-bottom:
|
|
236
|
+
gap: 8px;
|
|
237
|
+
margin-top: 8px;
|
|
238
|
+
margin-bottom: 8px;
|
|
239
239
|
}
|
|
240
240
|
.composition-card {
|
|
241
241
|
border: 1px solid #eaeaea;
|
|
@@ -259,12 +259,12 @@
|
|
|
259
259
|
color: #2c3e50;
|
|
260
260
|
}
|
|
261
261
|
.card-content {
|
|
262
|
-
padding:
|
|
262
|
+
padding: 8px 10px;
|
|
263
263
|
}
|
|
264
264
|
.card-stats {
|
|
265
265
|
display: flex;
|
|
266
266
|
flex-direction: column;
|
|
267
|
-
gap:
|
|
267
|
+
gap: 6px;
|
|
268
268
|
}
|
|
269
269
|
.stat-item {
|
|
270
270
|
display: flex;
|
|
@@ -292,9 +292,9 @@
|
|
|
292
292
|
.compact-grid {
|
|
293
293
|
display: grid;
|
|
294
294
|
grid-template-columns: repeat(2, 1fr);
|
|
295
|
-
gap:
|
|
296
|
-
margin-top:
|
|
297
|
-
margin-bottom:
|
|
295
|
+
gap: 6px;
|
|
296
|
+
margin-top: 6px;
|
|
297
|
+
margin-bottom: 6px;
|
|
298
298
|
}
|
|
299
299
|
|
|
300
300
|
.summary-card {
|
|
@@ -312,7 +312,7 @@
|
|
|
312
312
|
|
|
313
313
|
.indent {
|
|
314
314
|
margin-left: 12px;
|
|
315
|
-
margin-bottom:
|
|
315
|
+
margin-bottom: 4px;
|
|
316
316
|
}
|
|
317
317
|
|
|
318
318
|
.small {
|
|
@@ -330,13 +330,13 @@
|
|
|
330
330
|
}
|
|
331
331
|
|
|
332
332
|
.subcrate-composition h4 {
|
|
333
|
-
margin-top:
|
|
334
|
-
margin-bottom:
|
|
333
|
+
margin-top: 8px;
|
|
334
|
+
margin-bottom: 4px;
|
|
335
335
|
}
|
|
336
336
|
|
|
337
337
|
footer {
|
|
338
|
-
margin-top:
|
|
339
|
-
padding:
|
|
338
|
+
margin-top: 15px;
|
|
339
|
+
padding: 12px;
|
|
340
340
|
background-color: #f8f9fa;
|
|
341
341
|
border-top: 1px solid #ddd;
|
|
342
342
|
border-radius: 5px;
|
|
@@ -354,15 +354,15 @@
|
|
|
354
354
|
text-decoration: underline;
|
|
355
355
|
}
|
|
356
356
|
.regulatory-section {
|
|
357
|
-
margin-top:
|
|
358
|
-
margin-bottom:
|
|
357
|
+
margin-top: 10px;
|
|
358
|
+
margin-bottom: 10px;
|
|
359
359
|
padding: 0;
|
|
360
360
|
}
|
|
361
361
|
|
|
362
362
|
.regulatory-section h3 {
|
|
363
363
|
font-size: 18px;
|
|
364
364
|
color: #2c3e50;
|
|
365
|
-
margin-bottom:
|
|
365
|
+
margin-bottom: 8px;
|
|
366
366
|
border-bottom: none;
|
|
367
367
|
padding-bottom: 0;
|
|
368
368
|
}
|
|
@@ -370,12 +370,12 @@
|
|
|
370
370
|
.regulatory-grid {
|
|
371
371
|
display: grid;
|
|
372
372
|
grid-template-columns: repeat(2, 1fr);
|
|
373
|
-
gap:
|
|
373
|
+
gap: 8px;
|
|
374
374
|
}
|
|
375
375
|
|
|
376
376
|
.regulatory-item {
|
|
377
377
|
background-color: #f8f9fa;
|
|
378
|
-
padding:
|
|
378
|
+
padding: 8px 10px;
|
|
379
379
|
border-radius: 4px;
|
|
380
380
|
border: none;
|
|
381
381
|
}
|
|
@@ -388,9 +388,9 @@
|
|
|
388
388
|
|
|
389
389
|
.irb-detail {
|
|
390
390
|
background-color: #f8f9fa;
|
|
391
|
-
padding:
|
|
391
|
+
padding: 8px 10px;
|
|
392
392
|
border-radius: 4px;
|
|
393
|
-
margin-top:
|
|
393
|
+
margin-top: 8px;
|
|
394
394
|
}
|
|
395
395
|
|
|
396
396
|
.irb-org {
|
|
@@ -425,9 +425,9 @@
|
|
|
425
425
|
|
|
426
426
|
.regulatory-exemptions {
|
|
427
427
|
background-color: #f8f9fa;
|
|
428
|
-
padding:
|
|
428
|
+
padding: 8px 10px;
|
|
429
429
|
border-radius: 4px;
|
|
430
|
-
margin-top:
|
|
430
|
+
margin-top: 8px;
|
|
431
431
|
}
|
|
432
432
|
|
|
433
433
|
.regulatory-exemptions p {
|
|
@@ -504,9 +504,9 @@
|
|
|
504
504
|
/* START: Executive Summary and AI-Readiness Score styles */
|
|
505
505
|
.executive-summary-section {
|
|
506
506
|
background-color: #e9ecef;
|
|
507
|
-
padding:
|
|
507
|
+
padding: 12px;
|
|
508
508
|
border-radius: 5px;
|
|
509
|
-
margin-bottom:
|
|
509
|
+
margin-bottom: 10px;
|
|
510
510
|
border: 1px solid #ced4da;
|
|
511
511
|
}
|
|
512
512
|
|
|
@@ -521,18 +521,18 @@
|
|
|
521
521
|
.summary-description {
|
|
522
522
|
font-size: 13px;
|
|
523
523
|
color: #495057;
|
|
524
|
-
margin-bottom:
|
|
524
|
+
margin-bottom: 8px;
|
|
525
525
|
}
|
|
526
526
|
|
|
527
527
|
.exec-summary-grid {
|
|
528
528
|
display: grid;
|
|
529
529
|
grid-template-columns: 1fr 1fr;
|
|
530
|
-
gap:
|
|
530
|
+
gap: 10px;
|
|
531
531
|
}
|
|
532
532
|
|
|
533
533
|
.exec-summary-item {
|
|
534
534
|
background-color: #f8f9fa;
|
|
535
|
-
padding:
|
|
535
|
+
padding: 8px 10px;
|
|
536
536
|
border-radius: 4px;
|
|
537
537
|
border: 1px solid #e9ecef;
|
|
538
538
|
}
|
|
@@ -541,15 +541,15 @@
|
|
|
541
541
|
font-weight: bold;
|
|
542
542
|
color: #2c3e50;
|
|
543
543
|
display: block;
|
|
544
|
-
margin-bottom:
|
|
545
|
-
padding-bottom:
|
|
544
|
+
margin-bottom: 6px;
|
|
545
|
+
padding-bottom: 4px;
|
|
546
546
|
border-bottom: 1px solid #dee2e6;
|
|
547
547
|
font-size: 13px;
|
|
548
548
|
}
|
|
549
549
|
|
|
550
550
|
.exec-summary-item .stat-item {
|
|
551
551
|
display: block;
|
|
552
|
-
margin-bottom:
|
|
552
|
+
margin-bottom: 4px;
|
|
553
553
|
font-size: 12px;
|
|
554
554
|
}
|
|
555
555
|
|
|
@@ -297,9 +297,11 @@
|
|
|
297
297
|
</div>
|
|
298
298
|
</div>
|
|
299
299
|
</div>
|
|
300
|
+
{% if subcrate.preview_url %}
|
|
300
301
|
<div class="view-full-link">
|
|
301
302
|
<a href="{{ subcrate.preview_url }}">View Full Dataset Details</a>
|
|
302
303
|
</div>
|
|
304
|
+
{% endif %}
|
|
303
305
|
</div>
|
|
304
306
|
{% endfor %} {% else %}
|
|
305
307
|
<p>No subcrates found.</p>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<div id="datasheet-summary" class="executive-summary-section">
|
|
2
2
|
<h2>Datasheet Summary</h2>
|
|
3
3
|
{% if description %}
|
|
4
|
-
<p class="summary-description">{{ description }}</p>
|
|
4
|
+
<p class="summary-description">{{ description }}{% if description_truncated %}... <a href="#description" style="color: #2c3e50; font-size: 12px;">[read full description]</a>{% endif %}</p>
|
|
5
5
|
{% endif %}
|
|
6
6
|
|
|
7
7
|
<div class="exec-summary-grid">
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import pathlib
|
|
2
2
|
import json
|
|
3
3
|
from typing import List, Dict, Tuple, Set, Any
|
|
4
|
+
from fairscape_cli.utils.serialization import prune_none
|
|
4
5
|
|
|
5
6
|
def extract_datasets_from_graph(graph: List[Dict]) -> List[Tuple[str, bool]]:
|
|
6
7
|
"""
|
|
@@ -169,7 +170,7 @@ def add_inputs_outputs_to_rocrate(rocrate_path: pathlib.Path) -> Tuple[bool, str
|
|
|
169
170
|
metadata["@graph"] = graph
|
|
170
171
|
|
|
171
172
|
with open(metadata_path, 'w') as f:
|
|
172
|
-
json.dump(metadata, f, indent=2)
|
|
173
|
+
json.dump(prune_none(metadata), f, indent=2)
|
|
173
174
|
|
|
174
175
|
input_count = len(inputs)
|
|
175
176
|
output_count = len(outputs)
|
|
@@ -179,4 +180,4 @@ def add_inputs_outputs_to_rocrate(rocrate_path: pathlib.Path) -> Tuple[bool, str
|
|
|
179
180
|
except json.JSONDecodeError as e:
|
|
180
181
|
return False, f"Error parsing JSON: {e}"
|
|
181
182
|
except Exception as e:
|
|
182
|
-
return False, f"Unexpected error: {e}"
|
|
183
|
+
return False, f"Unexpected error: {e}"
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import pathlib
|
|
2
2
|
import json
|
|
3
|
+
from fairscape_cli.utils.serialization import prune_none
|
|
3
4
|
from typing import List, Tuple, Dict, Any
|
|
4
5
|
from rdflib import Graph, URIRef
|
|
5
6
|
from rdflib.namespace import OWL
|
|
@@ -192,7 +193,7 @@ def augment_rocrate_with_inverses(
|
|
|
192
193
|
if modified_count > 0:
|
|
193
194
|
try:
|
|
194
195
|
with open(metadata_file_path, 'w') as f:
|
|
195
|
-
json.dump(json_data, f, indent=2, ensure_ascii=False)
|
|
196
|
+
json.dump(prune_none(json_data), f, indent=2, ensure_ascii=False)
|
|
196
197
|
print(f"RO-Crate '{metadata_file_path}' augmented with inverse properties. {modified_count} modifications made.")
|
|
197
198
|
except Exception as e:
|
|
198
199
|
print(f"Error saving augmented RO-Crate JSON to {metadata_file_path}: {e}")
|
|
@@ -200,4 +201,4 @@ def augment_rocrate_with_inverses(
|
|
|
200
201
|
else:
|
|
201
202
|
print(f"No inverse properties needed to be added or RO-Crate '{metadata_file_path}' is already consistent.")
|
|
202
203
|
|
|
203
|
-
return True
|
|
204
|
+
return True
|
|
@@ -10,6 +10,7 @@ from pydantic import (
|
|
|
10
10
|
from typing import (
|
|
11
11
|
Optional
|
|
12
12
|
)
|
|
13
|
+
from fairscape_cli.utils.serialization import model_dump_pruned
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class BagIt(BaseModel):
|
|
@@ -94,7 +95,7 @@ class BagIt(BaseModel):
|
|
|
94
95
|
bagit_info_path = self.bagit_path / 'bag-info.txt'
|
|
95
96
|
|
|
96
97
|
with bagit_info_path.open(mode="w") as bag_info_file:
|
|
97
|
-
for key, value in self
|
|
98
|
+
for key, value in model_dump_pruned(self, by_alias=True).items():
|
|
98
99
|
if key != 'bagit_path' and key != 'rocrate_path':
|
|
99
100
|
bag_info_file.write('%s: %s\n' % (key, value))
|
|
100
101
|
|