4141 "xz" : lzma ,
4242}
4343
44+ GRAPH_MODES = {"download-url" }
45+
4446
4547def _get_download_directory (url : str , localDir : str | None ) -> str :
4648 """Return the local Databus-layout directory for a file URL."""
@@ -55,6 +57,43 @@ def _get_download_directory(url: str, localDir: str | None) -> str:
5557 return base_dir
5658
5759
60+ def _validate_graph_mode (graph_mode : str | None ) -> None :
61+ if graph_mode is not None and graph_mode not in GRAPH_MODES :
62+ raise ValueError (
63+ f"Unsupported graph mode: { graph_mode } . "
64+ f"Supported graph modes: { sorted (GRAPH_MODES )} "
65+ )
66+
67+
68+ def _write_graph_sidecars (
69+ final_file_paths : list [str ],
70+ source_url : str ,
71+ graph_mode : str | None ,
72+ ) -> None :
73+ """Write optional graph sidecars for final dataset outputs.
74+
75+ Existing downloads may be reused or transformed by the caller, but once a
76+ final output is considered successfully produced in graph mode, its sidecar
77+ must exist and point back to the source download URL.
78+ """
79+ if graph_mode is None :
80+ return
81+
82+ if graph_mode == "download-url" :
83+ for final_file_path in final_file_paths :
84+ with open (f"{ final_file_path } .graph" , "w" , encoding = "utf-8" ) as f :
85+ f .write (source_url )
86+
87+
88+ def _collect_files (directory : str ) -> list [str ]:
89+ return sorted (
90+ os .path .join (root , filename )
91+ for root , _dirs , filenames in os .walk (directory )
92+ for filename in filenames
93+ if not filename .endswith (".graph" )
94+ )
95+
96+
5897def _detect_compression_format (filename : str ) -> Optional [str ]:
5998 """Detect compression format from file extension.
6099
@@ -342,9 +381,7 @@ def _resolve_checksums_for_urls(file_urls: List[str], databus_key: str | None) -
342381 versions_map : dict = {}
343382 for file_url in file_urls :
344383 try :
345- host , accountId , groupId , artifactId , versionId , fileId = (
346- get_databus_id_parts_from_file_url (file_url )
347- )
384+ host , accountId , groupId , artifactId , versionId , fileId = get_databus_id_parts_from_file_url (file_url )
348385 except Exception :
349386 continue
350387 if versionId is None :
@@ -379,6 +416,7 @@ def _download_file(
379416 convert_format = None ,
380417 graph_name = None ,
381418 base_uri = None ,
419+ graph_mode = None ,
382420 validate_checksum : bool = False ,
383421 expected_checksum : str | None = None ,
384422 manifest_context = None ,
@@ -397,9 +435,12 @@ def _download_file(
397435 convert_format: Target RDF/tabular format for on-the-fly conversion.
398436 graph_name: Named graph URI for Triple -> Quad conversion (Layer 3).
399437 base_uri: Base URI for CSV -> Triple conversion (Layer 3).
438+ graph_mode: Optional graph sidecar mode. Currently supports 'download-url'.
400439 validate_checksum: Whether to validate checksums after downloading.
401440 expected_checksum: The expected checksum of the file.
402441 """
442+ _validate_graph_mode (graph_mode )
443+ source_url = url
403444 local_dir_was_given = localDir is not None
404445 localDir = _get_download_directory (url , localDir )
405446 if not local_dir_was_given :
@@ -582,6 +623,7 @@ def _download_file(
582623 needs_format_conversion = convert_format is not None
583624
584625 if not should_convert_compression and not needs_format_conversion :
626+ _write_graph_sidecars ([filename ], source_url , graph_mode )
585627 if manifest_context is not None :
586628 manifest_context .record_file (
587629 url = url ,
@@ -602,9 +644,7 @@ def _download_file(
602644 # Source file is uncompressed — compress it directly to
603645 # the target compression format.
604646 target_filepath = filename + COMPRESSION_EXTENSIONS [compression ]
605- print (
606- f"Compressing { file } -> { os .path .basename (target_filepath )} ..."
607- )
647+ print (f"Compressing { file } -> { os .path .basename (target_filepath )} ..." )
608648 with open (filename , "rb" ) as sf :
609649 with COMPRESSION_MODULES [compression ].open (
610650 target_filepath , "wb"
@@ -626,6 +666,7 @@ def _download_file(
626666 source_fmt ,
627667 compression ,
628668 )
669+ _write_graph_sidecars ([target_filepath ], source_url , graph_mode )
629670 if manifest_context is not None :
630671 manifest_context .record_file (
631672 url = url ,
@@ -649,10 +690,12 @@ def _download_file(
649690 file , source_fmt , compression
650691 )
651692 target_filepath = os .path .join (localDir , target_filename )
652- _convert_compression_format (
653- filename , target_filepath , source_fmt , compression
654- )
693+ _convert_compression_format (filename , target_filepath , source_fmt , compression )
694+ final_paths = [target_filepath ]
695+ else :
696+ final_paths = [filename ]
655697 # No format conversion needed, no further work.
698+ _write_graph_sidecars (final_paths , source_url , graph_mode )
656699 if manifest_context is not None :
657700 manifest_context .record_file (
658701 url = url ,
@@ -700,9 +743,7 @@ def _download_file(
700743 get_format_class (source_format_for_mapping )
701744 if source_format_for_mapping else None
702745 )
703- is_quad_to_triple = (
704- source_class_for_mapping == "quads" and target_class == "triples"
705- )
746+ is_quad_to_triple = (source_class_for_mapping == "quads" and target_class == "triples" )
706747
707748 if is_quad_to_triple :
708749 # Output directory name = original filename with compression and
@@ -720,6 +761,8 @@ def _download_file(
720761 graph_name = graph_name ,
721762 base_uri = base_uri ,
722763 )
764+ final_paths = _collect_files (output_dir )
765+ _write_graph_sidecars (final_paths , source_url , graph_mode )
723766
724767 # Delete the original downloaded (possibly compressed) file —
725768 # the split output directory replaces it.
@@ -787,6 +830,10 @@ def _download_file(
787830 shutil .copyfileobj (sf , tf )
788831
789832 os .remove (converted_uncompressed_path )
833+ final_paths = [recompressed_path ]
834+ else :
835+ final_paths = [converted_uncompressed_path ]
836+ _write_graph_sidecars (final_paths , source_url , graph_mode )
790837 finally :
791838 for temp_path in temp_paths :
792839 if os .path .exists (temp_path ):
@@ -803,6 +850,7 @@ def _download_file(
803850 downloaded_at = datetime .now (timezone .utc ).isoformat (),
804851 )
805852
853+
806854def _download_files (
807855 urls : List [str ],
808856 localDir : str ,
@@ -814,6 +862,7 @@ def _download_files(
814862 convert_format : str = None ,
815863 graph_name : str = None ,
816864 base_uri : str = None ,
865+ graph_mode : str = None ,
817866 manifest_context = None ,
818867 validate_checksum : bool = False ,
819868 checksums : dict | None = None ,
@@ -831,6 +880,7 @@ def _download_files(
831880 convert_format: Target RDF/tabular format for on-the-fly conversion.
832881 graph_name: Named graph URI for Triple -> Quad conversion (Layer 3).
833882 base_uri: Base URI for CSV -> Triple conversion (Layer 3).
883+ graph_mode: Optional graph sidecar mode. Currently supports 'download-url'.
834884 validate_checksum: Whether to validate checksums after downloading.
835885 checksums: Dictionary mapping URLs to their expected checksums.
836886 """
@@ -849,11 +899,13 @@ def _download_files(
849899 convert_format = convert_format ,
850900 graph_name = graph_name ,
851901 base_uri = base_uri ,
902+ graph_mode = graph_mode ,
852903 validate_checksum = validate_checksum ,
853904 expected_checksum = expected ,
854905 manifest_context = manifest_context ,
855906 )
856907
908+
857909def _get_sparql_query_of_collection (uri : str , databus_key : str | None = None ) -> str :
858910 """Get SPARQL query of collection members from databus collection URI.
859911
@@ -999,6 +1051,7 @@ def _download_collection(
9991051 convert_format : str = None ,
10001052 graph_name : str = None ,
10011053 base_uri : str = None ,
1054+ graph_mode : str = None ,
10021055 manifest_context = None ,
10031056 validate_checksum : bool = False ,
10041057) -> None :
@@ -1016,6 +1069,7 @@ def _download_collection(
10161069 convert_format: Target RDF/tabular format for on-the-fly conversion.
10171070 graph_name: Named graph URI for Triple -> Quad conversion (Layer 3).
10181071 base_uri: Base URI for CSV -> Triple conversion (Layer 3).
1072+ graph_mode: Optional graph sidecar mode. Currently supports 'download-url'.
10191073 validate_checksum: Whether to validate checksums after downloading.
10201074 """
10211075 query = _get_sparql_query_of_collection (uri , databus_key = databus_key )
@@ -1039,6 +1093,7 @@ def _download_collection(
10391093 convert_format = convert_format ,
10401094 graph_name = graph_name ,
10411095 base_uri = base_uri ,
1096+ graph_mode = graph_mode ,
10421097 manifest_context = manifest_context ,
10431098 validate_checksum = validate_checksum ,
10441099 checksums = checksums if checksums else None ,
@@ -1056,6 +1111,7 @@ def _download_version(
10561111 convert_format : str = None ,
10571112 graph_name : str = None ,
10581113 base_uri : str = None ,
1114+ graph_mode : str = None ,
10591115 manifest_context = None ,
10601116 validate_checksum : bool = False ,
10611117) -> None :
@@ -1072,6 +1128,7 @@ def _download_version(
10721128 convert_format: Target RDF/tabular format for on-the-fly conversion.
10731129 graph_name: Named graph URI for Triple -> Quad conversion (Layer 3).
10741130 base_uri: Base URI for CSV -> Triple conversion (Layer 3).
1131+ graph_mode: Optional graph sidecar mode. Currently supports 'download-url'.
10751132 validate_checksum: Whether to validate checksums after downloading.
10761133 """
10771134 json_str = fetch_databus_jsonld (uri , databus_key = databus_key )
@@ -1094,6 +1151,7 @@ def _download_version(
10941151 convert_format = convert_format ,
10951152 graph_name = graph_name ,
10961153 base_uri = base_uri ,
1154+ graph_mode = graph_mode ,
10971155 manifest_context = manifest_context ,
10981156 validate_checksum = validate_checksum ,
10991157 checksums = checksums ,
@@ -1112,6 +1170,7 @@ def _download_artifact(
11121170 convert_format : str = None ,
11131171 graph_name : str = None ,
11141172 base_uri : str = None ,
1173+ graph_mode : str = None ,
11151174 manifest_context = None ,
11161175 validate_checksum : bool = False ,
11171176) -> None :
@@ -1129,6 +1188,7 @@ def _download_artifact(
11291188 convert_format: Target RDF/tabular format for on-the-fly conversion.
11301189 graph_name: Named graph URI for Triple -> Quad conversion (Layer 3).
11311190 base_uri: Base URI for CSV -> Triple conversion (Layer 3).
1191+ graph_mode: Optional graph sidecar mode. Currently supports 'download-url'.
11321192 validate_checksum: Whether to validate checksums after downloading.
11331193 """
11341194 json_str = fetch_databus_jsonld (uri , databus_key = databus_key )
@@ -1157,6 +1217,7 @@ def _download_artifact(
11571217 convert_format = convert_format ,
11581218 graph_name = graph_name ,
11591219 base_uri = base_uri ,
1220+ graph_mode = graph_mode ,
11601221 manifest_context = manifest_context ,
11611222 validate_checksum = validate_checksum ,
11621223 checksums = checksums ,
@@ -1236,6 +1297,7 @@ def _download_group(
12361297 convert_format : str = None ,
12371298 graph_name : str = None ,
12381299 base_uri : str = None ,
1300+ graph_mode : str = None ,
12391301 manifest_context = None ,
12401302 validate_checksum : bool = False ,
12411303) -> None :
@@ -1253,6 +1315,7 @@ def _download_group(
12531315 convert_format: Target RDF/tabular format for on-the-fly conversion.
12541316 graph_name: Named graph URI for Triple -> Quad conversion (Layer 3).
12551317 base_uri: Base URI for CSV -> Triple conversion (Layer 3).
1318+ graph_mode: Optional graph sidecar mode. Currently supports 'download-url'.
12561319 validate_checksum: Whether to validate checksums after downloading.
12571320 """
12581321 json_str = fetch_databus_jsonld (uri , databus_key = databus_key )
@@ -1271,6 +1334,7 @@ def _download_group(
12711334 convert_format = convert_format ,
12721335 graph_name = graph_name ,
12731336 base_uri = base_uri ,
1337+ graph_mode = graph_mode ,
12741338 manifest_context = manifest_context ,
12751339 validate_checksum = validate_checksum ,
12761340 )
@@ -1323,6 +1387,7 @@ def download(
13231387 convert_format = None ,
13241388 graph_name = None ,
13251389 base_uri = None ,
1390+ graph_mode = None ,
13261391 validate_checksum : bool = False ,
13271392 manifest_context = None ,
13281393) -> None :
@@ -1343,12 +1408,12 @@ def download(
13431408 convert_format: Target RDF/tabular format for on-the-fly conversion.
13441409 graph_name: Named graph URI for Triple -> Quad conversion (Layer 3).
13451410 base_uri: Base URI for CSV -> Triple conversion (Layer 3).
1411+ graph_mode: Optional graph sidecar mode. Currently supports 'download-url'.
13461412 validate_checksum: Whether to validate checksums after downloading.
13471413 """
1414+ _validate_graph_mode (graph_mode )
13481415 for databusURI in databusURIs :
1349- host , account , group , artifact , version , file = (
1350- get_databus_id_parts_from_file_url (databusURI )
1351- )
1416+ host , account , group , artifact , version , file = get_databus_id_parts_from_file_url (databusURI )
13521417
13531418 # Determine endpoint per-URI if not explicitly provided
13541419 uri_endpoint = endpoint
@@ -1374,6 +1439,7 @@ def download(
13741439 convert_format ,
13751440 graph_name = graph_name ,
13761441 base_uri = base_uri ,
1442+ graph_mode = graph_mode ,
13771443 manifest_context = manifest_context ,
13781444 validate_checksum = validate_checksum ,
13791445 )
@@ -1398,6 +1464,7 @@ def download(
13981464 convert_format = convert_format ,
13991465 graph_name = graph_name ,
14001466 base_uri = base_uri ,
1467+ graph_mode = graph_mode ,
14011468 manifest_context = manifest_context ,
14021469 validate_checksum = validate_checksum ,
14031470 expected_checksum = expected ,
@@ -1415,6 +1482,7 @@ def download(
14151482 convert_format = convert_format ,
14161483 graph_name = graph_name ,
14171484 base_uri = base_uri ,
1485+ graph_mode = graph_mode ,
14181486 manifest_context = manifest_context ,
14191487 validate_checksum = validate_checksum ,
14201488 )
@@ -1434,6 +1502,7 @@ def download(
14341502 convert_format = convert_format ,
14351503 graph_name = graph_name ,
14361504 base_uri = base_uri ,
1505+ graph_mode = graph_mode ,
14371506 manifest_context = manifest_context ,
14381507 validate_checksum = validate_checksum ,
14391508 )
@@ -1453,6 +1522,7 @@ def download(
14531522 convert_format = convert_format ,
14541523 graph_name = graph_name ,
14551524 base_uri = base_uri ,
1525+ graph_mode = graph_mode ,
14561526 manifest_context = manifest_context ,
14571527 validate_checksum = validate_checksum ,
14581528 )
@@ -1494,7 +1564,8 @@ def download(
14941564 convert_format = convert_format ,
14951565 graph_name = graph_name ,
14961566 base_uri = base_uri ,
1567+ graph_mode = graph_mode ,
14971568 manifest_context = manifest_context ,
14981569 validate_checksum = validate_checksum ,
14991570 checksums = checksums if checksums else None ,
1500- )
1571+ )
0 commit comments