datamaker-py 0.7.0__tar.gz → 0.8.0__tar.gz

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.
Files changed (33) hide show
  1. {datamaker_py-0.7.0 → datamaker_py-0.8.0}/PKG-INFO +1 -1
  2. {datamaker_py-0.7.0 → datamaker_py-0.8.0}/pyproject.toml +1 -1
  3. {datamaker_py-0.7.0 → datamaker_py-0.8.0}/src/datamaker/main.py +143 -1
  4. {datamaker_py-0.7.0 → datamaker_py-0.8.0}/src/datamaker/routes/__init__.py +2 -0
  5. datamaker_py-0.8.0/src/datamaker/routes/keymaps.py +221 -0
  6. {datamaker_py-0.7.0 → datamaker_py-0.8.0}/src/datamaker_py.egg-info/PKG-INFO +1 -1
  7. {datamaker_py-0.7.0 → datamaker_py-0.8.0}/src/datamaker_py.egg-info/SOURCES.txt +1 -0
  8. {datamaker_py-0.7.0 → datamaker_py-0.8.0}/tests/test_routes.py +132 -0
  9. {datamaker_py-0.7.0 → datamaker_py-0.8.0}/LICENSE +0 -0
  10. {datamaker_py-0.7.0 → datamaker_py-0.8.0}/README.md +0 -0
  11. {datamaker_py-0.7.0 → datamaker_py-0.8.0}/setup.cfg +0 -0
  12. {datamaker_py-0.7.0 → datamaker_py-0.8.0}/src/datamaker/__init__.py +0 -0
  13. {datamaker_py-0.7.0 → datamaker_py-0.8.0}/src/datamaker/core.py +0 -0
  14. {datamaker_py-0.7.0 → datamaker_py-0.8.0}/src/datamaker/error.py +0 -0
  15. {datamaker_py-0.7.0 → datamaker_py-0.8.0}/src/datamaker/routes/api_keys.py +0 -0
  16. {datamaker_py-0.7.0 → datamaker_py-0.8.0}/src/datamaker/routes/base.py +0 -0
  17. {datamaker_py-0.7.0 → datamaker_py-0.8.0}/src/datamaker/routes/connections.py +0 -0
  18. {datamaker_py-0.7.0 → datamaker_py-0.8.0}/src/datamaker/routes/custom_types.py +0 -0
  19. {datamaker_py-0.7.0 → datamaker_py-0.8.0}/src/datamaker/routes/export_and_validation.py +0 -0
  20. {datamaker_py-0.7.0 → datamaker_py-0.8.0}/src/datamaker/routes/folders_and_utils.py +0 -0
  21. {datamaker_py-0.7.0 → datamaker_py-0.8.0}/src/datamaker/routes/generation.py +0 -0
  22. {datamaker_py-0.7.0 → datamaker_py-0.8.0}/src/datamaker/routes/projects.py +0 -0
  23. {datamaker_py-0.7.0 → datamaker_py-0.8.0}/src/datamaker/routes/scenario_files.py +0 -0
  24. {datamaker_py-0.7.0 → datamaker_py-0.8.0}/src/datamaker/routes/sets.py +0 -0
  25. {datamaker_py-0.7.0 → datamaker_py-0.8.0}/src/datamaker/routes/teams.py +0 -0
  26. {datamaker_py-0.7.0 → datamaker_py-0.8.0}/src/datamaker/routes/templates.py +0 -0
  27. {datamaker_py-0.7.0 → datamaker_py-0.8.0}/src/datamaker/routes/users.py +0 -0
  28. {datamaker_py-0.7.0 → datamaker_py-0.8.0}/src/datamaker/template.py +0 -0
  29. {datamaker_py-0.7.0 → datamaker_py-0.8.0}/src/datamaker_py.egg-info/dependency_links.txt +0 -0
  30. {datamaker_py-0.7.0 → datamaker_py-0.8.0}/src/datamaker_py.egg-info/requires.txt +0 -0
  31. {datamaker_py-0.7.0 → datamaker_py-0.8.0}/src/datamaker_py.egg-info/top_level.txt +0 -0
  32. {datamaker_py-0.7.0 → datamaker_py-0.8.0}/tests/test_integration.py +0 -0
  33. {datamaker_py-0.7.0 → datamaker_py-0.8.0}/tests/test_main.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datamaker-py
3
- Version: 0.7.0
3
+ Version: 0.8.0
4
4
  Summary: The official Python library for the Automators DataMaker API.
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "datamaker-py"
7
- version = "0.7.0"
7
+ version = "0.8.0"
8
8
  description = "The official Python library for the Automators DataMaker API."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1,6 +1,6 @@
1
1
  import os
2
2
  from dotenv import load_dotenv
3
- from typing import Optional, Dict
3
+ from typing import Optional, Dict, List
4
4
  from .routes.base import BaseClient
5
5
  from .routes.generation import GenerationClient
6
6
  from .routes.templates import TemplatesClient
@@ -22,6 +22,7 @@ from .routes.folders_and_utils import (
22
22
  from .routes.export_and_validation import ExportClient, ValidationClient
23
23
  from .routes.scenario_files import ScenarioFilesClient
24
24
  from .routes.sets import SetsClient
25
+ from .routes.keymaps import KeyMapsClient
25
26
 
26
27
  load_dotenv()
27
28
 
@@ -70,6 +71,7 @@ class DataMaker:
70
71
  api_key, default_headers, base_url, verify
71
72
  )
72
73
  self._sets = SetsClient(api_key, default_headers, base_url, verify)
74
+ self._keymaps = KeyMapsClient(api_key, default_headers, base_url, verify)
73
75
 
74
76
  # Maintain backward compatibility
75
77
  self.api_key = self._generation.api_key
@@ -793,6 +795,141 @@ class DataMaker:
793
795
  project_id=project_id,
794
796
  )
795
797
 
798
+ def get_keymaps(self, project_id: Optional[str] = None):
799
+ """List key maps for a project: one row per (mapName, object).
800
+
801
+ Args:
802
+ project_id: Optional project ID. Falls back to DATAMAKER_PROJECT_ID.
803
+
804
+ Returns:
805
+ A list of dictionaries with mapName, object, entryCount, updatedAt.
806
+ """
807
+ return self._keymaps.get_keymaps(project_id)
808
+
809
+ def keymap_put(
810
+ self,
811
+ map_name: str,
812
+ object: str,
813
+ entries: Dict[str, str],
814
+ run_id: Optional[str] = None,
815
+ project_id: Optional[str] = None,
816
+ ):
817
+ """Record old-to-new key mappings in a named key map (batch upsert).
818
+
819
+ Use after creating records in a target system to remember which source
820
+ key became which target key. Writing the same old key again overwrites
821
+ its new key. Batches are capped at 5000 entries per call.
822
+
823
+ Args:
824
+ map_name: Logical map name, e.g. "sap-material-migration".
825
+ object: The domain object type, e.g. "Material".
826
+ entries: Mapping of old key to new key.
827
+ run_id: Optional run/job id that minted these keys.
828
+ project_id: Optional project ID. Falls back to DATAMAKER_PROJECT_ID.
829
+
830
+ Returns:
831
+ A dictionary with mapName, object and upserted (count).
832
+
833
+ Example:
834
+ >>> dm = DataMaker()
835
+ >>> dm.keymap_put(
836
+ ... "sap-material-migration",
837
+ ... "Material",
838
+ ... {"MAT-001": "700001", "MAT-002": "700002"},
839
+ ... )
840
+ """
841
+ return self._keymaps.keymap_put(
842
+ map_name=map_name,
843
+ object=object,
844
+ entries=entries,
845
+ run_id=run_id,
846
+ project_id=project_id,
847
+ )
848
+
849
+ def keymap_lookup(
850
+ self,
851
+ map_name: str,
852
+ object: str,
853
+ old_keys: List[str],
854
+ project_id: Optional[str] = None,
855
+ ):
856
+ """Translate source-system keys to target-system keys (batch lookup).
857
+
858
+ Use when generating or migrating data that references records migrated
859
+ earlier. Lookups are capped at 5000 keys per call.
860
+
861
+ Args:
862
+ map_name: The key map to look up in.
863
+ object: The domain object type, e.g. "Material".
864
+ old_keys: Source-system keys to translate.
865
+ project_id: Optional project ID. Falls back to DATAMAKER_PROJECT_ID.
866
+
867
+ Returns:
868
+ A dictionary with mappings (found) and missing (no mapping yet).
869
+
870
+ Example:
871
+ >>> dm = DataMaker()
872
+ >>> result = dm.keymap_lookup(
873
+ ... "sap-material-migration", "Material", ["MAT-001", "MAT-999"]
874
+ ... )
875
+ """
876
+ return self._keymaps.keymap_lookup(
877
+ map_name=map_name,
878
+ object=object,
879
+ old_keys=old_keys,
880
+ project_id=project_id,
881
+ )
882
+
883
+ def get_keymap_entries(
884
+ self,
885
+ map_name: str,
886
+ object: Optional[str] = None,
887
+ page: int = 1,
888
+ page_size: int = 100,
889
+ project_id: Optional[str] = None,
890
+ ):
891
+ """Fetch a page of a key map's entries for inspection.
892
+
893
+ Args:
894
+ map_name: The key map to read.
895
+ object: Optional domain object type filter.
896
+ page: 1-based page number.
897
+ page_size: Entries per page (server-capped at 500).
898
+ project_id: Optional project ID. Falls back to DATAMAKER_PROJECT_ID.
899
+
900
+ Returns:
901
+ A dictionary with entries, total, page and pageSize.
902
+ """
903
+ return self._keymaps.get_keymap_entries(
904
+ map_name=map_name,
905
+ object=object,
906
+ page=page,
907
+ page_size=page_size,
908
+ project_id=project_id,
909
+ )
910
+
911
+ def delete_keymap(
912
+ self,
913
+ map_name: str,
914
+ object: Optional[str] = None,
915
+ project_id: Optional[str] = None,
916
+ ):
917
+ """Drop a key map (all its entries), optionally one object type only.
918
+
919
+ Args:
920
+ map_name: The key map to delete.
921
+ object: Optional domain object type to scope the delete to.
922
+ project_id: Optional project ID. Falls back to DATAMAKER_PROJECT_ID.
923
+
924
+ Returns:
925
+ Confirmation response with the deleted entry count.
926
+ """
927
+ return self._keymaps.delete_keymap(
928
+ map_name=map_name,
929
+ object=object,
930
+ project_id=project_id,
931
+ )
932
+
796
933
  # =================== PROPERTY ACCESS TO CLIENTS ===================
797
934
  # For advanced users who want direct access to specific clients
798
935
  @property
@@ -884,3 +1021,8 @@ class DataMaker:
884
1021
  def sets(self):
885
1022
  """Access to sets client."""
886
1023
  return self._sets
1024
+
1025
+ @property
1026
+ def keymaps(self):
1027
+ """Access to key maps client."""
1028
+ return self._keymaps
@@ -13,6 +13,7 @@ from .folders_and_utils import TemplateFoldersClient, ShortcutsClient, FeedbackC
13
13
  from .export_and_validation import ExportClient, ValidationClient
14
14
  from .scenario_files import ScenarioFilesClient
15
15
  from .sets import SetsClient
16
+ from .keymaps import KeyMapsClient
16
17
 
17
18
  __all__ = [
18
19
  "BaseClient",
@@ -34,4 +35,5 @@ __all__ = [
34
35
  "ValidationClient",
35
36
  "ScenarioFilesClient",
36
37
  "SetsClient",
38
+ "KeyMapsClient",
37
39
  ]
@@ -0,0 +1,221 @@
1
+ """Client for key map operations - old-to-new key mappings for migrations.
2
+
3
+ A "key map" records which source-system key became which target-system key
4
+ during a migration (e.g. legacy material number to new material number).
5
+ Unlike a set (one JSON blob), every mapping is its own row on the server, so
6
+ lookups are indexed and batch writes from parallel load workers are
7
+ concurrency-safe. Entries are unique per (project, map name, object, old key)
8
+ and the last write wins for the new key.
9
+ """
10
+
11
+ import os
12
+ from typing import Dict, List, Optional
13
+ from .base import BaseClient
14
+ from ..error import DataMakerError
15
+
16
+
17
+ class KeyMapsClient(BaseClient):
18
+ """Client for key map operations (old-to-new key mappings)."""
19
+
20
+ def get_keymaps(self, project_id: Optional[str] = None) -> List[Dict]:
21
+ """List key maps for a project: one row per (mapName, object).
22
+
23
+ Args:
24
+ project_id: Optional project ID to scope the listing to. Falls back
25
+ to the DATAMAKER_PROJECT_ID env var.
26
+
27
+ Returns:
28
+ A list of dictionaries with ``mapName``, ``object``, ``entryCount``
29
+ and ``updatedAt``.
30
+ """
31
+ project_id = project_id or os.environ.get("DATAMAKER_PROJECT_ID")
32
+
33
+ endpoint = "/keymaps"
34
+ if project_id:
35
+ endpoint += f"?projectId={project_id}"
36
+
37
+ response = self._make_request("GET", endpoint)
38
+ return response.json()
39
+
40
+ def keymap_put(
41
+ self,
42
+ map_name: str,
43
+ object: str,
44
+ entries: Dict[str, str],
45
+ run_id: Optional[str] = None,
46
+ project_id: Optional[str] = None,
47
+ ) -> Dict:
48
+ """Record old-to-new key mappings in a named key map (batch upsert).
49
+
50
+ Use after creating records in a target system to remember which source
51
+ key became which target key, so dependent data can reference the right
52
+ keys later. Writing the same old key again overwrites its new key.
53
+ Batches are capped at 5000 entries per call - page larger writes.
54
+
55
+ Args:
56
+ map_name: Logical map name grouping the entries, e.g.
57
+ "sap-material-migration".
58
+ object: The domain object type the keys belong to, e.g. "Material"
59
+ or "BusinessPartner".
60
+ entries: Mapping of old key to new key.
61
+ run_id: Optional run/job id that minted these keys.
62
+ project_id: Optional project ID. Falls back to DATAMAKER_PROJECT_ID.
63
+
64
+ Returns:
65
+ A dictionary with ``mapName``, ``object`` and ``upserted`` (count).
66
+
67
+ Raises:
68
+ DataMakerError: If ``map_name``, ``object`` or ``entries`` is empty.
69
+
70
+ Example:
71
+ >>> dm = DataMaker()
72
+ >>> dm.keymap_put(
73
+ ... "sap-material-migration",
74
+ ... "Material",
75
+ ... {"MAT-001": "700001", "MAT-002": "700002"},
76
+ ... )
77
+ """
78
+ if not map_name:
79
+ raise DataMakerError("map_name is required to put key map entries.")
80
+ if not object:
81
+ raise DataMakerError("object is required to put key map entries.")
82
+ if not entries:
83
+ raise DataMakerError("entries must not be empty.")
84
+
85
+ project_id = project_id or os.environ.get("DATAMAKER_PROJECT_ID")
86
+
87
+ payload: Dict = {
88
+ "mapName": map_name,
89
+ "object": object,
90
+ "entries": [
91
+ {"oldKey": old_key, "newKey": new_key}
92
+ for old_key, new_key in entries.items()
93
+ ],
94
+ }
95
+ if run_id is not None:
96
+ payload["runId"] = run_id
97
+ if project_id:
98
+ payload["projectId"] = project_id
99
+
100
+ response = self._make_request("POST", "/keymaps/entries", json=payload)
101
+ return response.json()
102
+
103
+ def keymap_lookup(
104
+ self,
105
+ map_name: str,
106
+ object: str,
107
+ old_keys: List[str],
108
+ project_id: Optional[str] = None,
109
+ ) -> Dict:
110
+ """Translate source-system keys to target-system keys (batch lookup).
111
+
112
+ Use when generating or migrating data that references records migrated
113
+ earlier (e.g. orders that need the NEW material numbers for OLD ones).
114
+ Lookups are capped at 5000 keys per call - page larger reads.
115
+
116
+ Args:
117
+ map_name: The key map to look up in.
118
+ object: The domain object type, e.g. "Material".
119
+ old_keys: Source-system keys to translate.
120
+ project_id: Optional project ID. Falls back to DATAMAKER_PROJECT_ID.
121
+
122
+ Returns:
123
+ A dictionary with ``mappings`` (old key to new key for the keys
124
+ that were found) and ``missing`` (keys with no mapping yet).
125
+
126
+ Raises:
127
+ DataMakerError: If ``map_name``, ``object`` or ``old_keys`` is empty.
128
+
129
+ Example:
130
+ >>> dm = DataMaker()
131
+ >>> result = dm.keymap_lookup(
132
+ ... "sap-material-migration", "Material", ["MAT-001", "MAT-999"]
133
+ ... )
134
+ >>> result["mappings"]
135
+ {'MAT-001': '700001'}
136
+ >>> result["missing"]
137
+ ['MAT-999']
138
+ """
139
+ if not map_name:
140
+ raise DataMakerError("map_name is required to look up key mappings.")
141
+ if not object:
142
+ raise DataMakerError("object is required to look up key mappings.")
143
+ if not old_keys:
144
+ raise DataMakerError("old_keys must not be empty.")
145
+
146
+ project_id = project_id or os.environ.get("DATAMAKER_PROJECT_ID")
147
+
148
+ payload: Dict = {
149
+ "mapName": map_name,
150
+ "object": object,
151
+ "oldKeys": old_keys,
152
+ }
153
+ if project_id:
154
+ payload["projectId"] = project_id
155
+
156
+ response = self._make_request("POST", "/keymaps/lookup", json=payload)
157
+ return response.json()
158
+
159
+ def get_keymap_entries(
160
+ self,
161
+ map_name: str,
162
+ object: Optional[str] = None,
163
+ page: int = 1,
164
+ page_size: int = 100,
165
+ project_id: Optional[str] = None,
166
+ ) -> Dict:
167
+ """Fetch a page of a key map's entries for inspection.
168
+
169
+ Args:
170
+ map_name: The key map to read.
171
+ object: Optional domain object type filter.
172
+ page: 1-based page number.
173
+ page_size: Entries per page (server-capped at 500).
174
+ project_id: Optional project ID. Falls back to DATAMAKER_PROJECT_ID.
175
+
176
+ Returns:
177
+ A dictionary with ``entries``, ``total``, ``page`` and ``pageSize``.
178
+ """
179
+ project_id = project_id or os.environ.get("DATAMAKER_PROJECT_ID")
180
+
181
+ params = [f"page={page}", f"pageSize={page_size}"]
182
+ if object:
183
+ params.append(f"object={object}")
184
+ if project_id:
185
+ params.append(f"projectId={project_id}")
186
+
187
+ endpoint = f"/keymaps/{map_name}/entries?" + "&".join(params)
188
+ response = self._make_request("GET", endpoint)
189
+ return response.json()
190
+
191
+ def delete_keymap(
192
+ self,
193
+ map_name: str,
194
+ object: Optional[str] = None,
195
+ project_id: Optional[str] = None,
196
+ ) -> Dict:
197
+ """Drop a key map (all its entries).
198
+
199
+ Args:
200
+ map_name: The key map to delete.
201
+ object: Optional domain object type - when given, only that
202
+ object's entries are dropped.
203
+ project_id: Optional project ID. Falls back to DATAMAKER_PROJECT_ID.
204
+
205
+ Returns:
206
+ Confirmation response with the deleted entry count.
207
+ """
208
+ project_id = project_id or os.environ.get("DATAMAKER_PROJECT_ID")
209
+
210
+ params = []
211
+ if object:
212
+ params.append(f"object={object}")
213
+ if project_id:
214
+ params.append(f"projectId={project_id}")
215
+
216
+ endpoint = f"/keymaps/{map_name}"
217
+ if params:
218
+ endpoint += "?" + "&".join(params)
219
+
220
+ response = self._make_request("DELETE", endpoint)
221
+ return response.json()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datamaker-py
3
- Version: 0.7.0
3
+ Version: 0.8.0
4
4
  Summary: The official Python library for the Automators DataMaker API.
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -14,6 +14,7 @@ src/datamaker/routes/custom_types.py
14
14
  src/datamaker/routes/export_and_validation.py
15
15
  src/datamaker/routes/folders_and_utils.py
16
16
  src/datamaker/routes/generation.py
17
+ src/datamaker/routes/keymaps.py
17
18
  src/datamaker/routes/projects.py
18
19
  src/datamaker/routes/scenario_files.py
19
20
  src/datamaker/routes/sets.py
@@ -12,6 +12,7 @@ from src.datamaker.routes.projects import ProjectsClient
12
12
  from src.datamaker.routes.users import UsersClient
13
13
  from src.datamaker.routes.teams import TeamsClient, TeamMembersClient
14
14
  from src.datamaker.routes.sets import SetsClient
15
+ from src.datamaker.routes.keymaps import KeyMapsClient
15
16
  from src.datamaker.routes.custom_types import EndpointsClient
16
17
  from src.datamaker.error import DataMakerError
17
18
 
@@ -690,3 +691,134 @@ class TestEndpointsClient:
690
691
  )
691
692
  assert result["authType"] == "Basic"
692
693
  assert result["basic"]["username"] == "user"
694
+
695
+ class TestKeyMapsClient:
696
+ """Test cases for the KeyMapsClient class."""
697
+
698
+ @patch("src.datamaker.routes.base.BaseClient._make_request")
699
+ def test_get_keymaps_with_project_id(self, mock_make_request, api_key):
700
+ """Test listing key maps scoped to a project id."""
701
+ mock_response = Mock()
702
+ mock_response.json.return_value = [
703
+ {"mapName": "m1", "object": "Material", "entryCount": 2}
704
+ ]
705
+ mock_make_request.return_value = mock_response
706
+
707
+ client = KeyMapsClient(api_key=api_key)
708
+ result = client.get_keymaps(project_id="proj-1")
709
+
710
+ mock_make_request.assert_called_once_with("GET", "/keymaps?projectId=proj-1")
711
+ assert result == [{"mapName": "m1", "object": "Material", "entryCount": 2}]
712
+
713
+ @patch("src.datamaker.routes.base.BaseClient._make_request")
714
+ def test_keymap_put(self, mock_make_request, api_key):
715
+ """Test batch-upserting key map entries."""
716
+ mock_response = Mock()
717
+ mock_response.json.return_value = {
718
+ "mapName": "sap-material-migration",
719
+ "object": "Material",
720
+ "upserted": 2,
721
+ }
722
+ mock_make_request.return_value = mock_response
723
+
724
+ client = KeyMapsClient(api_key=api_key)
725
+ result = client.keymap_put(
726
+ "sap-material-migration",
727
+ "Material",
728
+ {"MAT-001": "700001", "MAT-002": "700002"},
729
+ run_id="run-1",
730
+ project_id="proj-1",
731
+ )
732
+
733
+ mock_make_request.assert_called_once_with(
734
+ "POST",
735
+ "/keymaps/entries",
736
+ json={
737
+ "mapName": "sap-material-migration",
738
+ "object": "Material",
739
+ "entries": [
740
+ {"oldKey": "MAT-001", "newKey": "700001"},
741
+ {"oldKey": "MAT-002", "newKey": "700002"},
742
+ ],
743
+ "runId": "run-1",
744
+ "projectId": "proj-1",
745
+ },
746
+ )
747
+ assert result["upserted"] == 2
748
+
749
+ @patch("src.datamaker.routes.base.BaseClient._make_request")
750
+ def test_keymap_put_requires_entries(self, mock_make_request, api_key):
751
+ """Test that an empty entries dict raises."""
752
+ client = KeyMapsClient(api_key=api_key)
753
+ with pytest.raises(DataMakerError):
754
+ client.keymap_put("m1", "Material", {})
755
+ mock_make_request.assert_not_called()
756
+
757
+ @patch("src.datamaker.routes.base.BaseClient._make_request")
758
+ def test_keymap_lookup(self, mock_make_request, api_key):
759
+ """Test batch key lookup returns mappings and missing keys."""
760
+ mock_response = Mock()
761
+ mock_response.json.return_value = {
762
+ "mapName": "m1",
763
+ "object": "Material",
764
+ "mappings": {"MAT-001": "700001"},
765
+ "missing": ["MAT-999"],
766
+ }
767
+ mock_make_request.return_value = mock_response
768
+
769
+ client = KeyMapsClient(api_key=api_key)
770
+ result = client.keymap_lookup(
771
+ "m1", "Material", ["MAT-001", "MAT-999"], project_id="proj-1"
772
+ )
773
+
774
+ mock_make_request.assert_called_once_with(
775
+ "POST",
776
+ "/keymaps/lookup",
777
+ json={
778
+ "mapName": "m1",
779
+ "object": "Material",
780
+ "oldKeys": ["MAT-001", "MAT-999"],
781
+ "projectId": "proj-1",
782
+ },
783
+ )
784
+ assert result["mappings"] == {"MAT-001": "700001"}
785
+ assert result["missing"] == ["MAT-999"]
786
+
787
+ @patch("src.datamaker.routes.base.BaseClient._make_request")
788
+ def test_get_keymap_entries(self, mock_make_request, api_key):
789
+ """Test paginated entry inspection with an object filter."""
790
+ mock_response = Mock()
791
+ mock_response.json.return_value = {
792
+ "mapName": "m1",
793
+ "page": 1,
794
+ "pageSize": 50,
795
+ "total": 1,
796
+ "entries": [{"object": "Material", "oldKey": "a", "newKey": "b"}],
797
+ }
798
+ mock_make_request.return_value = mock_response
799
+
800
+ client = KeyMapsClient(api_key=api_key)
801
+ result = client.get_keymap_entries(
802
+ "m1", object="Material", page=1, page_size=50, project_id="proj-1"
803
+ )
804
+
805
+ mock_make_request.assert_called_once_with(
806
+ "GET",
807
+ "/keymaps/m1/entries?page=1&pageSize=50&object=Material&projectId=proj-1",
808
+ )
809
+ assert result["total"] == 1
810
+
811
+ @patch("src.datamaker.routes.base.BaseClient._make_request")
812
+ def test_delete_keymap(self, mock_make_request, api_key):
813
+ """Test dropping a key map scoped to one object type."""
814
+ mock_response = Mock()
815
+ mock_response.json.return_value = {"message": "Key map deleted", "deleted": 5}
816
+ mock_make_request.return_value = mock_response
817
+
818
+ client = KeyMapsClient(api_key=api_key)
819
+ result = client.delete_keymap("m1", object="Material", project_id="proj-1")
820
+
821
+ mock_make_request.assert_called_once_with(
822
+ "DELETE", "/keymaps/m1?object=Material&projectId=proj-1"
823
+ )
824
+ assert result["deleted"] == 5
File without changes
File without changes
File without changes