datamaker-py 0.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
datamaker/main.py ADDED
@@ -0,0 +1,886 @@
1
+ import os
2
+ from dotenv import load_dotenv
3
+ from typing import Optional, Dict
4
+ from .routes.base import BaseClient
5
+ from .routes.generation import GenerationClient
6
+ from .routes.templates import TemplatesClient
7
+ from .routes.api_keys import ApiKeysClient
8
+ from .routes.connections import ConnectionsClient
9
+ from .routes.projects import ProjectsClient
10
+ from .routes.users import UsersClient
11
+ from .routes.teams import TeamsClient, TeamMembersClient
12
+ from .routes.custom_types import (
13
+ CustomDataTypesClient,
14
+ EndpointFoldersClient,
15
+ EndpointsClient,
16
+ )
17
+ from .routes.folders_and_utils import (
18
+ TemplateFoldersClient,
19
+ ShortcutsClient,
20
+ FeedbackClient,
21
+ )
22
+ from .routes.export_and_validation import ExportClient, ValidationClient
23
+ from .routes.scenario_files import ScenarioFilesClient
24
+ from .routes.sets import SetsClient
25
+
26
+ load_dotenv()
27
+
28
+
29
+ class DataMaker:
30
+ """Main DataMaker client that provides access to all API functionality."""
31
+
32
+ def __init__(
33
+ self,
34
+ api_key: str = None,
35
+ default_headers: Dict[str, Optional[str]] = None,
36
+ base_url: Optional[str] = None,
37
+ verify: bool = True,
38
+ ):
39
+ if default_headers is None:
40
+ default_headers = {"Content-Type": "application/json"}
41
+
42
+ # Initialize all route clients
43
+ self._generation = GenerationClient(api_key, default_headers, base_url, verify)
44
+ self._templates = TemplatesClient(api_key, default_headers, base_url, verify)
45
+ self._api_keys = ApiKeysClient(api_key, default_headers, base_url, verify)
46
+ self._connections = ConnectionsClient(
47
+ api_key, default_headers, base_url, verify
48
+ )
49
+ self._projects = ProjectsClient(api_key, default_headers, base_url, verify)
50
+ self._users = UsersClient(api_key, default_headers, base_url, verify)
51
+ self._teams = TeamsClient(api_key, default_headers, base_url, verify)
52
+ self._team_members = TeamMembersClient(
53
+ api_key, default_headers, base_url, verify
54
+ )
55
+ self._custom_data_types = CustomDataTypesClient(
56
+ api_key, default_headers, base_url, verify
57
+ )
58
+ self._endpoint_folders = EndpointFoldersClient(
59
+ api_key, default_headers, base_url, verify
60
+ )
61
+ self._endpoints = EndpointsClient(api_key, default_headers, base_url, verify)
62
+ self._template_folders = TemplateFoldersClient(
63
+ api_key, default_headers, base_url, verify
64
+ )
65
+ self._shortcuts = ShortcutsClient(api_key, default_headers, base_url, verify)
66
+ self._feedback = FeedbackClient(api_key, default_headers, base_url, verify)
67
+ self._export = ExportClient(api_key, default_headers, base_url, verify)
68
+ self._validation = ValidationClient(api_key, default_headers, base_url, verify)
69
+ self._scenario_files = ScenarioFilesClient(
70
+ api_key, default_headers, base_url, verify
71
+ )
72
+ self._sets = SetsClient(api_key, default_headers, base_url, verify)
73
+
74
+ # Maintain backward compatibility
75
+ self.api_key = self._generation.api_key
76
+ self.headers = self._generation.headers
77
+ self.base_url = self._generation.base_url
78
+ self.verify = verify
79
+
80
+ # =================== GENERATION METHODS ===================
81
+ def generate(self, template):
82
+ """Generate data using a template."""
83
+ return self._generation.generate(template)
84
+
85
+ def generate_from_template_id(self, template_id: str, quantity: int = 10):
86
+ """Generate data from a template ID with specified quantity."""
87
+ # Get the template
88
+ template = self._templates.get_template_by_id(template_id)
89
+
90
+ # Set the quantity
91
+ template["quantity"] = quantity
92
+
93
+ # Generate data using the template
94
+ return self.generate(template)
95
+
96
+ # =================== TEMPLATE METHODS ===================
97
+ def get_templates(self):
98
+ """Fetch all templates from the API."""
99
+ return self._templates.get_templates()
100
+
101
+ def create_template(self, template_data: Dict, project_id: str, team_id: str):
102
+ """Create a new template."""
103
+ return self._templates.create_template(template_data, project_id, team_id)
104
+
105
+ def get_template(self, template_id: str):
106
+ """Get a specific template by ID."""
107
+ return self._templates.get_template(template_id)
108
+
109
+ def update_template(self, template_id: str, template_data: Dict):
110
+ """Update a template."""
111
+ return self._templates.update_template(template_id, template_data)
112
+
113
+ def delete_template(self, template_id: str):
114
+ """Delete a template."""
115
+ return self._templates.delete_template(template_id)
116
+
117
+ # =================== API KEY METHODS ===================
118
+ def get_api_keys(self):
119
+ """Get all API keys."""
120
+ return self._api_keys.get_api_keys()
121
+
122
+ def create_api_key(
123
+ self,
124
+ key: str,
125
+ scope: str,
126
+ name: Optional[str] = None,
127
+ team_id: Optional[str] = None,
128
+ ):
129
+ """Create a new API key."""
130
+ return self._api_keys.create_api_key(key, scope, name, team_id)
131
+
132
+ def update_api_key(
133
+ self,
134
+ key_id: str,
135
+ key: str,
136
+ name: Optional[str] = None,
137
+ team_id: Optional[str] = None,
138
+ ):
139
+ """Update an API key."""
140
+ return self._api_keys.update_api_key(key_id, key, name, team_id)
141
+
142
+ def delete_api_key(self, key_id: str):
143
+ """Delete an API key."""
144
+ return self._api_keys.delete_api_key(key_id)
145
+
146
+ # =================== CONNECTION METHODS ===================
147
+ def get_connections(self):
148
+ """Get all connections."""
149
+ return self._connections.get_connections()
150
+
151
+ def create_connection(
152
+ self,
153
+ name: str,
154
+ connection_type: str,
155
+ connection_string: str,
156
+ created_by: str,
157
+ project_id: str,
158
+ team_id: str,
159
+ **kwargs,
160
+ ):
161
+ """Create a new database connection."""
162
+ return self._connections.create_connection(
163
+ name,
164
+ connection_type,
165
+ connection_string,
166
+ created_by,
167
+ project_id,
168
+ team_id,
169
+ **kwargs,
170
+ )
171
+
172
+ def update_connection(
173
+ self,
174
+ connection_id: str,
175
+ name: str,
176
+ connection_type: str,
177
+ connection_string: str,
178
+ created_by: str,
179
+ project_id: str,
180
+ team_id: str,
181
+ **kwargs,
182
+ ):
183
+ """Update a database connection."""
184
+ return self._connections.update_connection(
185
+ connection_id,
186
+ name,
187
+ connection_type,
188
+ connection_string,
189
+ created_by,
190
+ project_id,
191
+ team_id,
192
+ **kwargs,
193
+ )
194
+
195
+ def delete_connection(self, connection_id: str):
196
+ """Delete a database connection."""
197
+ return self._connections.delete_connection(connection_id)
198
+
199
+ def test_connection(self, connection_data: Dict):
200
+ """Test a database connection."""
201
+ return self._connections.test_connection(connection_data)
202
+
203
+ def get_tables(self):
204
+ """Get all tables from connections."""
205
+ return self._connections.get_tables()
206
+
207
+ # =================== PROJECT METHODS ===================
208
+ def get_projects(self):
209
+ """Get all projects."""
210
+ return self._projects.get_projects()
211
+
212
+ def create_project(self, project_data: Dict, team_id: str):
213
+ """Create a new project."""
214
+ return self._projects.create_project(project_data, team_id)
215
+
216
+ def get_project(self, project_id: str):
217
+ """Get a specific project by ID."""
218
+ return self._projects.get_project(project_id)
219
+
220
+ def update_project(self, project_id: str, project_data: Dict):
221
+ """Update a project."""
222
+ return self._projects.update_project(project_id, project_data)
223
+
224
+ def delete_project(self, project_id: str):
225
+ """Delete a project."""
226
+ return self._projects.delete_project(project_id)
227
+
228
+ # =================== USER METHODS ===================
229
+ def get_users(self):
230
+ """Get all users."""
231
+ return self._users.get_users()
232
+
233
+ def create_user(self, user_data: Dict, user_id: str):
234
+ """Create a new user."""
235
+ return self._users.create_user(user_data, user_id)
236
+
237
+ def get_current_user(self):
238
+ """Get current user information."""
239
+ return self._users.get_current_user()
240
+
241
+ def provision_user(self, user_data: Dict):
242
+ """Provision a new user."""
243
+ return self._users.provision_user(user_data)
244
+
245
+ def update_user(self, user_id: str, user_data: Dict):
246
+ """Update a user."""
247
+ return self._users.update_user(user_id, user_data)
248
+
249
+ def patch_user(self, user_id: str, user_data: Dict):
250
+ """Partially update a user."""
251
+ return self._users.patch_user(user_id, user_data)
252
+
253
+ def delete_user(self, user_id: str):
254
+ """Delete a user."""
255
+ return self._users.delete_user(user_id)
256
+
257
+ # =================== TEAM METHODS ===================
258
+ def get_teams(self):
259
+ """Get all teams."""
260
+ return self._teams.get_teams()
261
+
262
+ def create_team(self, team_data: Dict):
263
+ """Create a new team."""
264
+ return self._teams.create_team(team_data)
265
+
266
+ def update_team(self, team_id: str, team_data: Dict):
267
+ """Update a team."""
268
+ return self._teams.update_team(team_id, team_data)
269
+
270
+ def delete_team(self, team_id: str):
271
+ """Delete a team."""
272
+ return self._teams.delete_team(team_id)
273
+
274
+ def setup_team(self, team_data: Dict):
275
+ """Setup a new team."""
276
+ return self._teams.setup_team(team_data)
277
+
278
+ # =================== TEAM MEMBER METHODS ===================
279
+ def get_team_members(self):
280
+ """Get all team members."""
281
+ return self._team_members.get_team_members()
282
+
283
+ def add_team_member(self, member_data: Dict):
284
+ """Add a new team member."""
285
+ return self._team_members.add_team_member(member_data)
286
+
287
+ def invite_team_member(self, invite_data: Dict):
288
+ """Invite a new team member."""
289
+ return self._team_members.invite_team_member(invite_data)
290
+
291
+ def update_team_member(self, member_id: str, member_data: Dict):
292
+ """Update a team member."""
293
+ return self._team_members.update_team_member(member_id, member_data)
294
+
295
+ def remove_team_member(self, member_id: str):
296
+ """Remove a team member."""
297
+ return self._team_members.remove_team_member(member_id)
298
+
299
+ # =================== CUSTOM DATA TYPE METHODS ===================
300
+ def get_custom_data_types(self, project_id: str):
301
+ """Get all custom data types for a specific project."""
302
+ return self._custom_data_types.get_custom_data_types(project_id)
303
+
304
+ def create_custom_data_type(self, data_type_data: Dict):
305
+ """Create a new custom data type."""
306
+ return self._custom_data_types.create_custom_data_type(data_type_data)
307
+
308
+ def update_custom_data_type(self, data_type_id: str, data_type_data: Dict):
309
+ """Update a custom data type."""
310
+ return self._custom_data_types.update_custom_data_type(
311
+ data_type_id, data_type_data
312
+ )
313
+
314
+ def delete_custom_data_type(self, data_type_id: str):
315
+ """Delete a custom data type."""
316
+ return self._custom_data_types.delete_custom_data_type(data_type_id)
317
+
318
+ # =================== ENDPOINT FOLDER METHODS ===================
319
+ def get_endpoint_folders(self):
320
+ """Get all endpoint folders."""
321
+ return self._endpoint_folders.get_endpoint_folders()
322
+
323
+ def create_endpoint_folder(self, folder_data: Dict):
324
+ """Create a new endpoint folder."""
325
+ return self._endpoint_folders.create_endpoint_folder(folder_data)
326
+
327
+ def update_endpoint_folder(self, folder_id: str, folder_data: Dict):
328
+ """Update an endpoint folder."""
329
+ return self._endpoint_folders.update_endpoint_folder(folder_id, folder_data)
330
+
331
+ def delete_endpoint_folder(self, folder_id: str):
332
+ """Delete an endpoint folder."""
333
+ return self._endpoint_folders.delete_endpoint_folder(folder_id)
334
+
335
+ # =================== ENDPOINT METHODS ===================
336
+ def get_endpoints(self):
337
+ """Get all endpoints."""
338
+ return self._endpoints.get_endpoints()
339
+
340
+ def create_endpoint(self, endpoint_data: Dict):
341
+ """Create a new endpoint."""
342
+ return self._endpoints.create_endpoint(endpoint_data)
343
+
344
+ def get_endpoint(self, endpoint_id: str):
345
+ """Get a specific endpoint by ID."""
346
+ return self._endpoints.get_endpoint(endpoint_id)
347
+
348
+ def update_endpoint(self, endpoint_id: str, endpoint_data: Dict):
349
+ """Update an endpoint."""
350
+ return self._endpoints.update_endpoint(endpoint_id, endpoint_data)
351
+
352
+ def delete_endpoint(self, endpoint_id: str):
353
+ """Delete an endpoint."""
354
+ return self._endpoints.delete_endpoint(endpoint_id)
355
+
356
+ def resolve_endpoint_auth(self, endpoint_id: str):
357
+ """Resolve an endpoint's real, decrypted credentials (Authorization
358
+ header + Basic username/password). See EndpointsClient for details."""
359
+ return self._endpoints.resolve_endpoint_auth(endpoint_id)
360
+
361
+ # =================== TEMPLATE FOLDER METHODS ===================
362
+ def get_template_folders(self):
363
+ """Get all template folders."""
364
+ return self._template_folders.get_template_folders()
365
+
366
+ def create_template_folder(self, folder_data: Dict):
367
+ """Create a new template folder."""
368
+ return self._template_folders.create_template_folder(folder_data)
369
+
370
+ def update_template_folder(self, folder_id: str, folder_data: Dict):
371
+ """Update a template folder."""
372
+ return self._template_folders.update_template_folder(folder_id, folder_data)
373
+
374
+ def delete_template_folder(self, folder_id: str):
375
+ """Delete a template folder."""
376
+ return self._template_folders.delete_template_folder(folder_id)
377
+
378
+ # =================== SHORTCUT METHODS ===================
379
+ def get_shortcuts(self):
380
+ """Get all shortcuts."""
381
+ return self._shortcuts.get_shortcuts()
382
+
383
+ def create_shortcut(self, shortcut_data: Dict):
384
+ """Create a new shortcut."""
385
+ return self._shortcuts.create_shortcut(shortcut_data)
386
+
387
+ def update_shortcut(self, shortcut_id: str, shortcut_data: Dict):
388
+ """Update a shortcut."""
389
+ return self._shortcuts.update_shortcut(shortcut_id, shortcut_data)
390
+
391
+ def delete_shortcut(self, shortcut_id: str):
392
+ """Delete a shortcut."""
393
+ return self._shortcuts.delete_shortcut(shortcut_id)
394
+
395
+ # =================== FEEDBACK METHODS ===================
396
+ def get_feedback(self):
397
+ """Get all feedback."""
398
+ return self._feedback.get_feedback()
399
+
400
+ def submit_feedback(self, feedback_data: Dict):
401
+ """Submit new feedback."""
402
+ return self._feedback.submit_feedback(feedback_data)
403
+
404
+ def update_feedback(self, feedback_id: str, feedback_data: Dict):
405
+ """Update feedback."""
406
+ return self._feedback.update_feedback(feedback_id, feedback_data)
407
+
408
+ def delete_feedback(self, feedback_id: str):
409
+ """Delete feedback."""
410
+ return self._feedback.delete_feedback(feedback_id)
411
+
412
+ # =================== EXPORT METHODS ===================
413
+ def export_to_rest(self, export_data: Dict):
414
+ """Export data to REST API."""
415
+ return self._export.export_to_rest(export_data)
416
+
417
+ def export_to_database(self, export_data: Dict):
418
+ """Export data to database."""
419
+ return self._export.export_to_database(export_data)
420
+
421
+ # =================== VALIDATION METHODS ===================
422
+ def validate_api_key(self):
423
+ """Test API key authentication."""
424
+ return self._validation.validate_api_key()
425
+
426
+ # =================== SCENARIO FILE METHODS ===================
427
+ def get_scenario_files(self, scenario_id: Optional[str] = None):
428
+ """Get all files for a scenario or all accessible files."""
429
+ return self._scenario_files.get_scenario_files(scenario_id)
430
+
431
+ def get_scenario_file(self, file_id: str, scenario_id: Optional[str] = None):
432
+ """Get metadata for a specific file by ID.
433
+
434
+ Args:
435
+ file_id: The unique identifier of the file.
436
+ scenario_id: Optional scenario ID. Falls back to DATAMAKER_SCENARIO_ID env var.
437
+
438
+ Returns:
439
+ File metadata dictionary with refreshed presigned URL.
440
+ """
441
+ return self._scenario_files.get_scenario_file(file_id, scenario_id)
442
+
443
+ def download_scenario_file(self, file_id: str, scenario_id: Optional[str] = None):
444
+ """Download a file's content by ID.
445
+
446
+ Args:
447
+ file_id: The unique identifier of the file.
448
+ scenario_id: Optional scenario ID. Falls back to DATAMAKER_SCENARIO_ID env var.
449
+
450
+ Returns:
451
+ The file content as bytes.
452
+ """
453
+ return self._scenario_files.download_scenario_file(file_id, scenario_id)
454
+
455
+ def download_scenario_file_to_path(
456
+ self, file_id: str, destination_path: str, scenario_id: Optional[str] = None
457
+ ):
458
+ """Download a file and save it to a local path.
459
+
460
+ Args:
461
+ file_id: The unique identifier of the file.
462
+ destination_path: Local file path to save the downloaded content.
463
+ scenario_id: Optional scenario ID. Falls back to DATAMAKER_SCENARIO_ID env var.
464
+
465
+ Returns:
466
+ The destination path where the file was saved.
467
+ """
468
+ return self._scenario_files.download_scenario_file_to_path(
469
+ file_id, destination_path, scenario_id
470
+ )
471
+
472
+ def create_scenario_file(
473
+ self,
474
+ name: str,
475
+ content,
476
+ scenario_id: Optional[str] = None,
477
+ team_id: Optional[str] = None,
478
+ project_id: Optional[str] = None,
479
+ description: Optional[str] = None,
480
+ mime_type: Optional[str] = None,
481
+ folder_id: Optional[str] = None,
482
+ ):
483
+ """Create/upload a new file in a scenario.
484
+
485
+ Args:
486
+ name: The filename (e.g., 'data.json', 'config.yaml').
487
+ content: The file content - can be a string, bytes, or file-like object.
488
+ scenario_id: Optional scenario ID. Falls back to DATAMAKER_SCENARIO_ID env var.
489
+ team_id: Optional team ID. Falls back to DATAMAKER_TEAM_ID env var.
490
+ project_id: Optional project ID. Falls back to DATAMAKER_PROJECT_ID env var.
491
+ description: Optional description of the file.
492
+ mime_type: Optional MIME type. If not provided, will be guessed from filename.
493
+ folder_id: Optional folder ID to place the file in.
494
+
495
+ Returns:
496
+ The created file metadata dictionary.
497
+ """
498
+ return self._scenario_files.create_scenario_file(
499
+ name=name,
500
+ content=content,
501
+ scenario_id=scenario_id,
502
+ team_id=team_id,
503
+ project_id=project_id,
504
+ description=description,
505
+ mime_type=mime_type,
506
+ folder_id=folder_id,
507
+ )
508
+
509
+ def upload_scenario_file_from_path(
510
+ self,
511
+ file_path: str,
512
+ scenario_id: Optional[str] = None,
513
+ team_id: Optional[str] = None,
514
+ project_id: Optional[str] = None,
515
+ name: Optional[str] = None,
516
+ description: Optional[str] = None,
517
+ folder_id: Optional[str] = None,
518
+ folder: str = "uploads",
519
+ ):
520
+ """Upload a file from a local path to a scenario.
521
+
522
+ Args:
523
+ file_path: Path to the local file to upload.
524
+ scenario_id: Optional scenario ID. Falls back to DATAMAKER_SCENARIO_ID env var.
525
+ team_id: Optional team ID (deprecated, not used for multipart upload).
526
+ project_id: Optional project ID (deprecated, not used for multipart upload).
527
+ name: Optional filename. If not provided, uses the original filename.
528
+ description: Optional description (deprecated, not used for multipart upload).
529
+ folder_id: Optional folder ID (deprecated, not used for multipart upload).
530
+ folder: Folder to upload to ("uploads" or "outputs", default: "uploads").
531
+
532
+ Returns:
533
+ The created file metadata dictionary.
534
+ """
535
+ return self._scenario_files.upload_scenario_file_from_path(
536
+ file_path=file_path,
537
+ scenario_id=scenario_id,
538
+ team_id=team_id,
539
+ project_id=project_id,
540
+ name=name,
541
+ description=description,
542
+ folder_id=folder_id,
543
+ folder=folder,
544
+ )
545
+
546
+ def delete_scenario_file(self, file_id: str, scenario_id: Optional[str] = None):
547
+ """Delete a file by ID.
548
+
549
+ Args:
550
+ file_id: The unique identifier of the file to delete.
551
+ scenario_id: Optional scenario ID. Falls back to DATAMAKER_SCENARIO_ID env var.
552
+
553
+ Returns:
554
+ Confirmation response.
555
+ """
556
+ return self._scenario_files.delete_scenario_file(file_id, scenario_id)
557
+
558
+ def read_file_by_path(
559
+ self,
560
+ file_path: str,
561
+ storage_base_url: Optional[str] = None,
562
+ ):
563
+ """Read a file directly from storage by its path.
564
+
565
+ This method allows you to read files from the storage bucket using the
566
+ file path copied from the workspace files modal. It's useful for accessing
567
+ files uploaded to scenarios from within Python scripts.
568
+
569
+ Args:
570
+ file_path: The file path (key) in storage. This is typically copied from
571
+ the workspace files modal using the "Copy path" button.
572
+ Example: "scenarios/scenario-id/uploads/filename.txt"
573
+ storage_base_url: Optional base URL for the storage bucket. If not provided,
574
+ uses the DATAMAKER_STORAGE_URL environment variable or a default.
575
+
576
+ Returns:
577
+ The file content as bytes.
578
+
579
+ Example:
580
+ >>> dm = DataMaker(api_key="your-key")
581
+ >>> # Copy path from UI: "scenarios/abc123/uploads/data.json"
582
+ >>> content = dm.read_file_by_path("scenarios/abc123/uploads/data.json")
583
+ >>> data = json.loads(content.decode('utf-8'))
584
+ """
585
+ return self._scenario_files.read_file_by_path(file_path, storage_base_url)
586
+
587
+ def read_file_by_path_as_text(
588
+ self,
589
+ file_path: str,
590
+ storage_base_url: Optional[str] = None,
591
+ encoding: str = "utf-8",
592
+ ):
593
+ """Read a file directly from storage by its path and return as text.
594
+
595
+ Convenience method that reads a file and decodes it as text.
596
+
597
+ Args:
598
+ file_path: The file path (key) in storage.
599
+ storage_base_url: Optional base URL for the storage bucket.
600
+ encoding: Text encoding to use (default: 'utf-8').
601
+
602
+ Returns:
603
+ The file content as a string.
604
+
605
+ Example:
606
+ >>> dm = DataMaker(api_key="your-key")
607
+ >>> content = dm.read_file_by_path_as_text("scenarios/abc123/uploads/data.txt")
608
+ >>> print(content)
609
+ """
610
+ return self._scenario_files.read_file_by_path_as_text(
611
+ file_path, storage_base_url, encoding
612
+ )
613
+
614
+ def save_file(
615
+ self,
616
+ file_path: str,
617
+ scenario_id: Optional[str] = None,
618
+ team_id: Optional[str] = None,
619
+ project_id: Optional[str] = None,
620
+ name: Optional[str] = None,
621
+ description: Optional[str] = None,
622
+ folder_id: Optional[str] = None,
623
+ folder: str = "uploads",
624
+ ) -> Dict:
625
+ """Convenience method to save a local file to workspace storage.
626
+
627
+ This method simplifies uploading files by automatically pulling required
628
+ context (scenario_id) from environment variables if not provided.
629
+ This is especially useful in DataMaker sandbox environments where these
630
+ variables are pre-configured.
631
+
632
+ Args:
633
+ file_path: Path to the local file to save to workspace.
634
+ scenario_id: Optional scenario ID. Falls back to DATAMAKER_SCENARIO_ID env var.
635
+ team_id: Optional team ID (deprecated, not used).
636
+ project_id: Optional project ID (deprecated, not used).
637
+ name: Optional filename. If not provided, uses the original filename.
638
+ description: Optional description (deprecated, not used).
639
+ folder_id: Optional folder ID (deprecated, not used).
640
+ folder: Folder to upload to ("uploads" or "outputs", default: "uploads").
641
+
642
+ Returns:
643
+ The created file metadata dictionary.
644
+
645
+ Raises:
646
+ DataMakerError: If scenario_id is not provided and not available in environment.
647
+
648
+ Example:
649
+ >>> # In a DataMaker sandbox environment:
650
+ >>> dm = DataMaker()
651
+ >>> result = dm.save_file("test_file.txt")
652
+ >>> print(f"File saved: {result['name']}")
653
+
654
+ >>> # Or specify IDs explicitly:
655
+ >>> result = dm.save_file(
656
+ ... "test_file.txt",
657
+ ... scenario_id="scenario-123"
658
+ ... )
659
+ """
660
+ return self._scenario_files.save_file(
661
+ file_path=file_path,
662
+ scenario_id=scenario_id,
663
+ team_id=team_id,
664
+ project_id=project_id,
665
+ name=name,
666
+ description=description,
667
+ folder_id=folder_id,
668
+ folder=folder,
669
+ )
670
+
671
+ # =================== SET METHODS ===================
672
+ def get_sets(self, project_id: Optional[str] = None):
673
+ """Get all saved sets for the caller's project/team scope.
674
+
675
+ Args:
676
+ project_id: Optional project ID to scope the listing to. Falls back
677
+ to the DATAMAKER_PROJECT_ID env var.
678
+
679
+ Returns:
680
+ A list of set metadata dictionaries.
681
+ """
682
+ return self._sets.get_sets(project_id)
683
+
684
+ def get_set(self, set_id: str):
685
+ """Get a single saved set by ID, including its full rows payload.
686
+
687
+ Args:
688
+ set_id: The unique identifier of the set.
689
+
690
+ Returns:
691
+ The set dictionary (including its saved rows in ``data``).
692
+ """
693
+ return self._sets.get_set(set_id)
694
+
695
+ def create_set(
696
+ self,
697
+ name: str,
698
+ data=None,
699
+ description: Optional[str] = None,
700
+ row_count: Optional[int] = None,
701
+ project_id: Optional[str] = None,
702
+ ):
703
+ """Create (save) a new set.
704
+
705
+ Args:
706
+ name: A name for the saved set.
707
+ data: The rows payload to save - typically a list of row dicts.
708
+ description: Optional short description of what the set captures.
709
+ row_count: Optional explicit row count (derived from data otherwise).
710
+ project_id: Optional project ID. Falls back to DATAMAKER_PROJECT_ID.
711
+
712
+ Returns:
713
+ The created set dictionary.
714
+ """
715
+ return self._sets.create_set(
716
+ name=name,
717
+ data=data,
718
+ description=description,
719
+ row_count=row_count,
720
+ project_id=project_id,
721
+ )
722
+
723
+ def update_set(
724
+ self,
725
+ set_id: str,
726
+ name: Optional[str] = None,
727
+ description: Optional[str] = None,
728
+ data=None,
729
+ row_count: Optional[int] = None,
730
+ ):
731
+ """Update a saved set.
732
+
733
+ Args:
734
+ set_id: The unique identifier of the set to update.
735
+ name: New name for the set.
736
+ description: New description.
737
+ data: Replacement rows payload.
738
+ row_count: Explicit row count override.
739
+
740
+ Returns:
741
+ The updated set dictionary.
742
+ """
743
+ return self._sets.update_set(
744
+ set_id,
745
+ name=name,
746
+ description=description,
747
+ data=data,
748
+ row_count=row_count,
749
+ )
750
+
751
+ def delete_set(self, set_id: str):
752
+ """Delete a saved set by ID.
753
+
754
+ Args:
755
+ set_id: The unique identifier of the set to delete.
756
+
757
+ Returns:
758
+ Confirmation response.
759
+ """
760
+ return self._sets.delete_set(set_id)
761
+
762
+ def save_set(
763
+ self,
764
+ name: str,
765
+ data,
766
+ description: Optional[str] = None,
767
+ project_id: Optional[str] = None,
768
+ ):
769
+ """Convenience method to save rows as a named set.
770
+
771
+ Project context is pulled from DATAMAKER_PROJECT_ID when not provided,
772
+ which is convenient inside DataMaker sandbox environments.
773
+
774
+ Args:
775
+ name: A name for the saved set.
776
+ data: The rows to save - typically a list of row dicts.
777
+ description: Optional short description of what the set captures.
778
+ project_id: Optional project ID. Falls back to DATAMAKER_PROJECT_ID.
779
+
780
+ Returns:
781
+ The created set dictionary.
782
+
783
+ Example:
784
+ >>> dm = DataMaker()
785
+ >>> rows = dm.generate(template)
786
+ >>> result = dm.save_set("nightly-regression", rows)
787
+ >>> print(f"Saved set: {result['name']} ({result['rowCount']} rows)")
788
+ """
789
+ return self._sets.save_set(
790
+ name=name,
791
+ data=data,
792
+ description=description,
793
+ project_id=project_id,
794
+ )
795
+
796
+ # =================== PROPERTY ACCESS TO CLIENTS ===================
797
+ # For advanced users who want direct access to specific clients
798
+ @property
799
+ def generation(self):
800
+ """Access to generation client."""
801
+ return self._generation
802
+
803
+ @property
804
+ def templates(self):
805
+ """Access to templates client."""
806
+ return self._templates
807
+
808
+ @property
809
+ def api_keys(self):
810
+ """Access to API keys client."""
811
+ return self._api_keys
812
+
813
+ @property
814
+ def connections(self):
815
+ """Access to connections client."""
816
+ return self._connections
817
+
818
+ @property
819
+ def projects(self):
820
+ """Access to projects client."""
821
+ return self._projects
822
+
823
+ @property
824
+ def users(self):
825
+ """Access to users client."""
826
+ return self._users
827
+
828
+ @property
829
+ def teams(self):
830
+ """Access to teams client."""
831
+ return self._teams
832
+
833
+ @property
834
+ def team_members(self):
835
+ """Access to team members client."""
836
+ return self._team_members
837
+
838
+ @property
839
+ def custom_data_types(self):
840
+ """Access to custom data types client."""
841
+ return self._custom_data_types
842
+
843
+ @property
844
+ def endpoint_folders(self):
845
+ """Access to endpoint folders client."""
846
+ return self._endpoint_folders
847
+
848
+ @property
849
+ def endpoints(self):
850
+ """Access to endpoints client."""
851
+ return self._endpoints
852
+
853
+ @property
854
+ def template_folders(self):
855
+ """Access to template folders client."""
856
+ return self._template_folders
857
+
858
+ @property
859
+ def shortcuts(self):
860
+ """Access to shortcuts client."""
861
+ return self._shortcuts
862
+
863
+ @property
864
+ def feedback(self):
865
+ """Access to feedback client."""
866
+ return self._feedback
867
+
868
+ @property
869
+ def export(self):
870
+ """Access to export client."""
871
+ return self._export
872
+
873
+ @property
874
+ def validation(self):
875
+ """Access to validation client."""
876
+ return self._validation
877
+
878
+ @property
879
+ def scenario_files(self):
880
+ """Access to scenario files client."""
881
+ return self._scenario_files
882
+
883
+ @property
884
+ def sets(self):
885
+ """Access to sets client."""
886
+ return self._sets