4242from atomworks .io .utils .bonds import get_struct_conn_dict_from_atom_array
4343from atomworks .io .utils .ccd import check_ccd_codes_are_available
4444from atomworks .io .utils .chain import create_chain_id_generator
45- from atomworks .io .utils .io_utils import get_structure , infer_pdb_file_type , read_any
45+ from atomworks .io .utils .io_utils import (
46+ apply_sharding_pattern ,
47+ build_sharding_pattern ,
48+ get_structure ,
49+ infer_pdb_file_type ,
50+ read_any ,
51+ )
4652from atomworks .io .utils .non_rcsb import (
4753 get_identity_assembly_gen_category ,
4854 get_identity_op_expr_category ,
5359
5460__all__ = ["parse" ]
5561
56- DEFAULT_PARSE_KWARGS = {
62+ STANDARD_PARSER_ARGS = {
5763 "add_missing_atoms" : True ,
5864 "add_id_and_entity_annotations" : True ,
5965 "add_bond_types_from_struct_conn" : ["covale" ],
6066 "remove_ccds" : CRYSTALLIZATION_AIDS ,
6167 "remove_waters" : True ,
6268 "fix_ligands_at_symmetry_centers" : True ,
63- "hydrogen_policy" : "keep" ,
6469 "fix_arginines" : True ,
6570 "fix_formal_charges" : True ,
66- "convert_mse_to_met" : True ,
67- "build_assembly" : "all" ,
71+ "fix_bond_types" : True ,
72+ "convert_mse_to_met" : True , # Changed from False to True vs. atomworks.io.parser.parse default
73+ "hydrogen_policy" : "keep" ,
74+ "model" : None , # all models
6875}
69- """Some fairly standard parsing arguments that can be imported for convenience."""
76+ """Common cif parser arguments for `atomworks.io.parse` for many biomolecular use cases.
77+
78+ Similar to the defaults below but additionally converts selenomethionine (MSE) residues to methionine (MET) residues,
79+ which is desirable for many practical applications but would not be appropriate as a universal default.
80+
81+ This dictionary exists to provide a convenient import for the standard parameters.
82+ """
83+
84+ # Cache sharding configuration (internal, not exposed to parse() to avoid complexity)
85+ _CACHE_SHARDING_DEPTH = 2 # Use 2-level sharding by default (e.g., ab/cd/abcdef123456/)
86+ _CACHE_SHARDING_CHARS_PER_DIR = 2 # Number of characters per directory level
7087
7188
7289def _get_atomworks_version () -> str :
@@ -79,6 +96,32 @@ def _get_atomworks_version() -> str:
7996 return "unknown"
8097
8198
99+ def _parse_args_to_hash (parse_arguments : dict [str , Any ], truncate : int = 8 ) -> str :
100+ """Compute hash from parse arguments with sorted keys."""
101+ args_string = "," .join (str (parse_arguments [k ]) for k in sorted (parse_arguments .keys ()))
102+ return string_to_md5_hash (args_string , truncate = truncate )
103+
104+
105+ def _build_cache_file_path (
106+ cache_dir : Path ,
107+ args_hash : str ,
108+ filename : os .PathLike ,
109+ assembly_info : str ,
110+ ) -> Path :
111+ """Build sharded cache file path for parsed structure."""
112+ structure_id = Path (filename ).stem
113+
114+ # Pad structure ID to minimum required length for sharding
115+ min_length = _CACHE_SHARDING_DEPTH * _CACHE_SHARDING_CHARS_PER_DIR
116+ structure_id_padded = structure_id .ljust (min_length , "_" )
117+
118+ # Build sharded path
119+ sharding_pattern = build_sharding_pattern (depth = _CACHE_SHARDING_DEPTH , chars_per_dir = _CACHE_SHARDING_CHARS_PER_DIR )
120+ sharded_path = apply_sharding_pattern (structure_id_padded , sharding_pattern )
121+
122+ return cache_dir / args_hash / sharded_path / f"{ structure_id } _assembly_{ assembly_info } .pkl.gz"
123+
124+
82125def parse (
83126 filename : os .PathLike | io .StringIO | io .BytesIO ,
84127 * ,
@@ -182,12 +225,11 @@ def parse(
182225 Should typically not be used directly.
183226
184227 """
185- # CCD mirror
186228 if ccd_mirror_path and not os .path .exists (ccd_mirror_path ):
187- logger .warning (
188- f"Local mirror of the Chemical Component Dictionary does not exist: { ccd_mirror_path } . Falling back to Biotite's built-in CCD."
229+ raise FileNotFoundError (
230+ f"Local mirror of the Chemical Component Dictionary does not exist: { ccd_mirror_path } . "
231+ "To use Biotite's built-in CCD, set `ccd_mirror_path` to None."
189232 )
190- ccd_mirror_path = None
191233
192234 # Set default value for remove_ccds if None
193235 if remove_ccds is None :
@@ -232,15 +274,13 @@ def parse(
232274 "convert_mse_to_met" : convert_mse_to_met ,
233275 "hydrogen_policy" : hydrogen_policy ,
234276 }
235- # Compose args_string from parse_arguments values (in order)
236- args_string = "," .join (str (parse_arguments [k ]) for k in parse_arguments )
237- args_hash = string_to_md5_hash (args_string , truncate = 8 )
277+ args_hash = _parse_args_to_hash (parse_arguments )
238278
239279 # ... generate assembly info
240280 assembly_info = "," .join (build_assembly ) if isinstance (build_assembly , list | tuple ) else build_assembly
241281
242- # ... construct the full cache file path
243- cache_file_path = cache_dir / args_hash / f" { Path ( filename ). stem } _assembly_ { assembly_info } .pkl.gz"
282+ # ... construct the full cache file path with sharding
283+ cache_file_path = _build_cache_file_path ( cache_dir , args_hash , filename , assembly_info )
244284
245285 # If we are loading from cache, try to load the result from the cache
246286 if load_from_cache :
@@ -268,8 +308,7 @@ def parse(
268308 result ["assemblies" ] = assemblies
269309 return result
270310 except Exception as e :
271- # Log an error, and continue to parse from CIF
272- logger .error (f"Error loading from cache: { e } " )
311+ raise RuntimeError (f"Error loading from cache: { e } , tried path: { cache_file_path } " ) from e
273312
274313 if file_type == "pdb" :
275314 result = _parse_from_pdb (
@@ -315,9 +354,9 @@ def parse(
315354
316355 if not is_buffer and save_to_cache and cache_dir and (not cache_file_path .exists ()):
317356 # We want our cache to include:
318- # (1) All keys in `result` excep the assemblies and
319- # (2) The information needed to rebuild the assembly(s), which is stored in `result["extra_info"]`
320- # (3) The parse_arguments and atomworks.io version
357+ # (1) All keys in `result` except the assemblies; and,
358+ # (2) The information needed to rebuild the assembly(s), which is stored in `result["extra_info"]`; and,
359+ # (3) The parse_arguments and atomworks version
321360
322361 # Add parse_arguments and version to metadata before saving
323362 result .setdefault ("metadata" , {}).update (
0 commit comments