1717)
1818from pathlib import Path
1919from datetime import date , datetime
20- from typing_extensions import TypeGuard
20+ from typing_extensions import TypeGuard , get_args
2121
2222import sniffio
2323
24- from .._types import Omit , NotGiven , FileTypes , HeadersLike
24+ from .._types import Omit , NotGiven , FileTypes , ArrayFormat , HeadersLike
2525
2626_T = TypeVar ("_T" )
2727_TupleT = TypeVar ("_TupleT" , bound = Tuple [object , ...])
@@ -40,25 +40,45 @@ def extract_files(
4040 query : Mapping [str , object ],
4141 * ,
4242 paths : Sequence [Sequence [str ]],
43+ array_format : ArrayFormat = "brackets" ,
4344) -> list [tuple [str , FileTypes ]]:
4445 """Recursively extract files from the given dictionary based on specified paths.
4546
4647 A path may look like this ['foo', 'files', '<array>', 'data'].
4748
49+ ``array_format`` controls how ``<array>`` segments contribute to the emitted
50+ field name. Supported values: ``"brackets"`` (``foo[]``), ``"repeat"`` and
51+ ``"comma"`` (``foo``), ``"indices"`` (``foo[0]``, ``foo[1]``).
52+
4853 Note: this mutates the given dictionary.
4954 """
5055 files : list [tuple [str , FileTypes ]] = []
5156 for path in paths :
52- files .extend (_extract_items (query , path , index = 0 , flattened_key = None ))
57+ files .extend (_extract_items (query , path , index = 0 , flattened_key = None , array_format = array_format ))
5358 return files
5459
5560
61+ def _array_suffix (array_format : ArrayFormat , array_index : int ) -> str :
62+ if array_format == "brackets" :
63+ return "[]"
64+ if array_format == "indices" :
65+ return f"[{ array_index } ]"
66+ if array_format == "repeat" or array_format == "comma" :
67+ # Both repeat the bare field name for each file part; there is no
68+ # meaningful way to comma-join binary parts.
69+ return ""
70+ raise NotImplementedError (
71+ f"Unknown array_format value: { array_format } , choose from { ', ' .join (get_args (ArrayFormat ))} "
72+ )
73+
74+
5675def _extract_items (
5776 obj : object ,
5877 path : Sequence [str ],
5978 * ,
6079 index : int ,
6180 flattened_key : str | None ,
81+ array_format : ArrayFormat ,
6282) -> list [tuple [str , FileTypes ]]:
6383 try :
6484 key = path [index ]
@@ -75,9 +95,11 @@ def _extract_items(
7595
7696 if is_list (obj ):
7797 files : list [tuple [str , FileTypes ]] = []
78- for entry in obj :
79- assert_is_file_content (entry , key = flattened_key + "[]" if flattened_key else "" )
80- files .append ((flattened_key + "[]" , cast (FileTypes , entry )))
98+ for array_index , entry in enumerate (obj ):
99+ suffix = _array_suffix (array_format , array_index )
100+ emitted_key = (flattened_key + suffix ) if flattened_key else suffix
101+ assert_is_file_content (entry , key = emitted_key )
102+ files .append ((emitted_key , cast (FileTypes , entry )))
81103 return files
82104
83105 assert_is_file_content (obj , key = flattened_key )
@@ -106,6 +128,7 @@ def _extract_items(
106128 path ,
107129 index = index ,
108130 flattened_key = flattened_key ,
131+ array_format = array_format ,
109132 )
110133 elif is_list (obj ):
111134 if key != "<array>" :
@@ -117,9 +140,12 @@ def _extract_items(
117140 item ,
118141 path ,
119142 index = index ,
120- flattened_key = flattened_key + "[]" if flattened_key is not None else "[]" ,
143+ flattened_key = (
144+ (flattened_key if flattened_key is not None else "" ) + _array_suffix (array_format , array_index )
145+ ),
146+ array_format = array_format ,
121147 )
122- for item in obj
148+ for array_index , item in enumerate ( obj )
123149 ]
124150 )
125151
0 commit comments