@@ -155,43 +155,31 @@ def create(
155155 def create_many (
156156 self ,
157157 label : str ,
158- data : Union [ Dict [ str , Any ], List [Dict [str , Any ] ]],
159- options : Optional [Dict [str , bool ]] = None ,
158+ data : List [Dict [str , Any ]],
159+ options : Optional [Dict [str , Any ]] = None ,
160160 transaction : Optional [Transaction ] = None ,
161161 ) -> List [Record ]:
162- """Create multiple records in a single operation.
162+ """Create multiple flat records in a single operation.
163+
164+ This helper maps directly to the ``/records/import/json`` endpoint and is
165+ intended for CSV-like flat rows (no nested objects or arrays). For nested
166+ or complex JSON payloads, use :meth:`import_json` instead.
163167
164- Creates multiple records with the same label but different data.
165- This is more efficient than creating records individually when
166- you need to insert many records at once .
168+ The behaviour mirrors the TypeScript SDK ``records.createMany`` method,
169+ including support for upsert semantics via ``options.mergeBy`` and
170+ ``options.mergeStrategy`` .
167171
168172 Args:
169- label (str): The label/type to assign to all new records.
170- data (Union[Dict[str, Any], List[Dict[str, Any]]]): The data for the records.
171- Can be a single dictionary or a list of dictionaries.
172- options (Optional[Dict[str, bool]], optional): Configuration options for the operation.
173- Available options:
174- - returnResult (bool): Whether to return the created records data. Defaults to True.
175- - suggestTypes (bool): Whether to automatically suggest data types. Defaults to True.
176- transaction (Optional[Transaction], optional): Transaction context for the operation.
177- If provided, the operation will be part of the transaction. Defaults to None.
173+ label: The label/type to assign to all new records.
174+ data: A list of flat dictionaries. Each dictionary represents a single
175+ record. Nested objects/arrays are not supported here.
176+ options: Optional write options forwarded as-is to the server
177+ (e.g. ``suggestTypes``, ``mergeBy``, ``mergeStrategy``, etc.).
178+ transaction: Optional transaction context for the operation.
178179
179180 Returns:
180- List[Record]: A list of Record objects representing the newly created records.
181-
182- Raises:
183- ValueError: If the label is empty or data is invalid.
184- RequestError: If the server request fails.
185-
186- Example:
187- >>> records_api = RecordsAPI(client)
188- >>> users_data = [
189- ... {"name": "John Doe", "email": "john@example.com"},
190- ... {"name": "Jane Smith", "email": "jane@example.com"}
191- ... ]
192- >>> new_records = records_api.create_many("User", users_data)
193- >>> print(len(new_records))
194- 2
181+ List[Record]: A list of Record objects representing the created
182+ (or upserted) records when ``options.returnResult`` is true.
195183 """
196184 headers = Transaction ._build_transaction_header (transaction )
197185
@@ -205,6 +193,120 @@ def create_many(
205193 )
206194 return [Record (self .client , record ) for record in response .get ("data" )]
207195
196+ def import_json (
197+ self ,
198+ data : Any ,
199+ label : Optional [str ] = None ,
200+ options : Optional [Dict [str , Any ]] = None ,
201+ transaction : Optional [Transaction ] = None ,
202+ ) -> List [Record ]:
203+ """Import nested or complex JSON payloads.
204+
205+ Works in two modes:
206+
207+ - With ``label`` provided: imports ``data`` under the given label.
208+ - Without ``label``: expects ``data`` to be a mapping with a single
209+ top-level key that determines the label, e.g. ``{"ITEM": [...]}``.
210+
211+ This mirrors the behaviour of the TypeScript SDK ``records.importJson``
212+ method and is suitable for nested, mixed, or hash-map-like JSON
213+ structures.
214+
215+ Args:
216+ data: Arbitrary JSON-serialisable structure to import.
217+ label: Optional label; if omitted, inferred from a single top-level
218+ key of ``data``.
219+ options: Optional import/write options (see server docs).
220+ transaction: Optional transaction context for the operation.
221+
222+ Returns:
223+ List[Record]: Imported records when ``options.returnResult`` is true.
224+
225+ Raises:
226+ ValueError: If ``label`` is omitted and ``data`` is not an object
227+ with a single top-level key.
228+ """
229+
230+ inferred_label = label
231+ payload_data : Any = data
232+
233+ if inferred_label is None :
234+ if isinstance (data , dict ):
235+ keys = list (data .keys ())
236+ if len (keys ) == 1 :
237+ inferred_label = keys [0 ]
238+ payload_data = data [inferred_label ]
239+ else :
240+ raise ValueError (
241+ "records.import_json: Missing `label`. Provide `label` or "
242+ "pass an object with a single top-level key that determines "
243+ "the label, e.g. { ITEM: [...] }."
244+ )
245+ else :
246+ raise ValueError (
247+ "records.import_json: Missing `label`. Provide `label` or pass "
248+ "an object with a single top-level key that determines the "
249+ "label, e.g. { ITEM: [...] }."
250+ )
251+
252+ headers = Transaction ._build_transaction_header (transaction )
253+ payload = {
254+ "label" : inferred_label ,
255+ "data" : payload_data ,
256+ "options" : options or {"returnResult" : True , "suggestTypes" : True },
257+ }
258+
259+ response = self .client ._make_request (
260+ "POST" , "/records/import/json" , payload , headers
261+ )
262+ return [Record (self .client , record ) for record in response .get ("data" )]
263+
264+ def upsert (
265+ self ,
266+ data : Dict [str , Any ],
267+ label : Optional [str ] = None ,
268+ options : Optional [Dict [str , Any ]] = None ,
269+ transaction : Optional [Transaction ] = None ,
270+ ) -> Record :
271+ """Upsert a single record.
272+
273+ Attempts to find an existing record matching the provided criteria and
274+ either updates it or creates a new one. This mirrors the behaviour of the
275+ TypeScript SDK ``records.upsert`` method.
276+
277+ Args:
278+ data: A flat dictionary containing the record data.
279+ label: Optional label/type of the record.
280+ options: Optional upsert options, including ``mergeBy`` and
281+ ``mergeStrategy`` as well as standard write options.
282+ transaction: Optional transaction context for the operation.
283+
284+ Returns:
285+ Record: The upserted record instance.
286+ """
287+
288+ headers = Transaction ._build_transaction_header (transaction )
289+
290+ # Ensure upsert semantics always receive a mergeBy array by default.
291+ # This mirrors the JavaScript SDK behaviour where mergeBy defaults to []
292+ # and the server interprets an empty array as "use all incoming keys".
293+ normalized_options : Dict [str , Any ] = {
294+ "returnResult" : True ,
295+ "suggestTypes" : True ,
296+ "mergeBy" : [],
297+ }
298+ if options :
299+ normalized_options .update (options )
300+
301+ payload : Dict [str , Any ] = {
302+ "label" : label ,
303+ "data" : data ,
304+ "options" : normalized_options ,
305+ }
306+
307+ response = self .client ._make_request ("POST" , "/records" , payload , headers )
308+ return Record (self .client , response .get ("data" ))
309+
208310 def attach (
209311 self ,
210312 source : Union [str , Dict [str , Any ]],
0 commit comments