@@ -144,7 +144,7 @@ def add_prov_from_git(
144144) -> None :
145145 """Add dct:created and dct:modified to RDF files based on git history.
146146
147- For each .ttl file in vocab_dir:
147+ For each .ttl file in vocab_dir (including subdirectories) :
148148 - dct:created: Add only if missing (from git first commit date)
149149 - dct:modified: Update if different from git last commit date (logs info message)
150150
@@ -161,19 +161,20 @@ def add_prov_from_git(
161161 vocab_dir = Path (vocab_dir )
162162 git_lookup_dir = Path (source_dir ) if source_dir else vocab_dir
163163
164- # Get all .ttl files in the directory
165- ttl_files = list (vocab_dir .glob ("*.ttl" ))
164+ # Get all .ttl files in the directory (including subdirectories)
165+ ttl_files = list (vocab_dir .rglob ("*.ttl" ))
166166 if not ttl_files :
167167 logger .warning ("No .ttl files found in %s" , vocab_dir )
168168 return
169169
170170 # Get git info from the source directory (or vocab_dir if no source specified)
171171 git_info = get_directory_git_info (git_lookup_dir , repo_dir )
172172
173- # Check that all .ttl files are tracked in git (by filename )
173+ # Check that all .ttl files are tracked in git (by relative path )
174174 for ttl_file in ttl_files :
175- # Build the path as it would appear in the source directory
176- source_file = git_lookup_dir / ttl_file .name
175+ # Preserve subdirectory structure when looking up in source directory
176+ rel_to_vocab = ttl_file .relative_to (vocab_dir )
177+ source_file = git_lookup_dir / rel_to_vocab
177178 try :
178179 rel_path = source_file .relative_to (repo_dir )
179180 except ValueError :
@@ -186,8 +187,9 @@ def add_prov_from_git(
186187
187188 # Process each .ttl file
188189 for ttl_file in ttl_files :
189- # Build the path as it would appear in the source directory for git lookup
190- source_file = git_lookup_dir / ttl_file .name
190+ # Preserve subdirectory structure when looking up in source directory
191+ rel_to_vocab = ttl_file .relative_to (vocab_dir )
192+ source_file = git_lookup_dir / rel_to_vocab
191193 try :
192194 rel_path = source_file .relative_to (repo_dir )
193195 except ValueError :
@@ -306,14 +308,39 @@ def extract_numeric_id_from_iri(iri):
306308 return "" .join (reversed (reverse_id ))
307309
308310
311+ PARTITION_SIZE = 1000 # Number of IDs per subdirectory partition
312+
313+
314+ def get_partition_dir_name (numeric_id_str : str , id_length : int = 7 ) -> str :
315+ """Compute subdirectory name for a given numeric ID.
316+
317+ Partitions IDs into subdirectories of PARTITION_SIZE (1000) IDs each.
318+ Directory name padding matches vocabulary's id_length:
319+ - 7-digit IDs: IDs0000xxx, IDs0001xxx, ...
320+ - 6-digit IDs: IDs000xxx, IDs001xxx, ...
321+
322+ Args:
323+ numeric_id_str: String representation of the numeric ID (e.g., "0000016")
324+ id_length: The configured ID length for the vocabulary (default 7)
325+
326+ Returns:
327+ Partition directory name (e.g., "IDs0000xxx" for 7-digit IDs)
328+ """
329+ numeric_value = int (numeric_id_str ) if numeric_id_str else 0
330+ partition_num = numeric_value // PARTITION_SIZE
331+ prefix_width = id_length - 3 # 'xxx' represents last 3 digits
332+ return f"IDs{ partition_num :0{prefix_width }d} xxx"
333+
334+
309335def write_split_turtle (
310336 vocab_graph : Graph , outdir : Path , vocab_name : str | None = None
311337) -> None :
312338 """
313339 Write each concept, collection and concept scheme to a separate turtle file.
314340
315341 The ids are used as filenames. Schema:Person and schema:Organization entities
316- are included in the concept_scheme.ttl file.
342+ are included in the concept_scheme.ttl file. Concepts and collections are
343+ partitioned into subdirectories by ID range (1000 IDs per directory).
317344
318345 Args:
319346 vocab_graph: The vocabulary graph to split.
@@ -324,6 +351,13 @@ def write_split_turtle(
324351 outdir .mkdir (exist_ok = True )
325352 query = "SELECT ?iri WHERE {?iri a %s.}"
326353
354+ # Get id_length from config (default 7 if not configured)
355+ id_length = 7
356+ if vocab_name :
357+ vocab_config = config .IDRANGES .vocabs .get (vocab_name .lower ())
358+ if vocab_config :
359+ id_length = vocab_config .id_length
360+
327361 for skos_class in ["skos:Concept" , "skos:Collection" , "skos:ConceptScheme" ]:
328362 qresults = vocab_graph .query (query % skos_class , initNs = {"skos" : SKOS })
329363 # Iterate over search results and write each concept, collection and
@@ -343,7 +377,11 @@ def write_split_turtle(
343377 tmp_graph += vocab_graph .triples ((entity_iri , None , None ))
344378 outfile = outdir / "concept_scheme.ttl"
345379 else :
346- outfile = outdir / f"{ id_part } .ttl"
380+ # Partition concepts and collections into subdirectories by ID range
381+ partition_dir = get_partition_dir_name (id_part , id_length )
382+ partition_path = outdir / partition_dir
383+ partition_path .mkdir (exist_ok = True )
384+ outfile = partition_path / f"{ id_part } .ttl"
347385 tmp_graph .serialize (destination = outfile , format = "longturtle" )
348386 logger .debug ("-> wrote %i %ss-file(s)." , len (qresults ), skos_class )
349387
@@ -442,7 +480,7 @@ def transform(args):
442480 logger .warning ("Unsupported filetype: %s" , args .VOCAB )
443481
444482 if args .join :
445- rdf_dirs = [d for d in Path (args .VOCAB ).iterdir () if any (d .glob ("*.ttl" ))]
483+ rdf_dirs = [d for d in Path (args .VOCAB ).iterdir () if any (d .rglob ("*.ttl" ))]
446484 else :
447485 rdf_dirs = []
448486
@@ -489,11 +527,11 @@ def transform(args):
489527 # Determine vocabulary directories to process:
490528 # - If VOCAB contains .ttl files directly, it's a single vocabulary directory
491529 # - Otherwise, look for subdirectories containing .ttl files (like vocabularies/)
492- if any (args .VOCAB .glob ("*.ttl" )):
530+ if any (args .VOCAB .rglob ("*.ttl" )):
493531 vocab_dirs = [args .VOCAB ]
494532 else :
495533 vocab_dirs = [
496- d for d in args .VOCAB .iterdir () if d .is_dir () and any (d .glob ("*.ttl" ))
534+ d for d in args .VOCAB .iterdir () if d .is_dir () and any (d .rglob ("*.ttl" ))
497535 ]
498536 if not vocab_dirs :
499537 msg = f'--prov-from-git requires a directory with .ttl files or subdirectories containing .ttl files, got: "{ args .VOCAB } "'
0 commit comments