Skip to content

Commit 6688d6e

Browse files
tpellissierclaude
andcommitted
Add optional select parameter to client.tables.list()
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent a0e1163 commit 6688d6e

4 files changed

Lines changed: 144 additions & 7 deletions

File tree

src/PowerPlatform/Dataverse/data/_odata.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1423,7 +1423,11 @@ def _get_table_info(self, table_schema_name: str) -> Optional[Dict[str, Any]]:
14231423
"columns_created": [],
14241424
}
14251425

1426-
def _list_tables(self, filter: Optional[str] = None) -> List[Dict[str, Any]]:
1426+
def _list_tables(
1427+
self,
1428+
filter: Optional[str] = None,
1429+
select: Optional[List[str]] = None,
1430+
) -> List[Dict[str, Any]]:
14271431
"""List all non-private tables (``IsPrivate eq false``).
14281432
14291433
:param filter: Optional additional OData ``$filter`` expression that is
@@ -1433,6 +1437,12 @@ def _list_tables(self, filter: Optional[str] = None) -> List[Dict[str, Any]]:
14331437
When ``None`` (the default), only the ``IsPrivate eq false`` filter
14341438
is applied.
14351439
:type filter: ``str`` or ``None``
1440+
:param select: Optional list of property names to project via
1441+
``$select``. Values are passed as-is (PascalCase) because
1442+
``EntityDefinitions`` uses PascalCase property names.
1443+
When ``None`` (the default), no ``$select`` is applied and all
1444+
properties are returned.
1445+
:type select: ``list[str]`` or ``None``
14361446
14371447
:return: Metadata entries for non-private tables (may be empty).
14381448
:rtype: ``list[dict[str, Any]]``
@@ -1445,7 +1455,9 @@ def _list_tables(self, filter: Optional[str] = None) -> List[Dict[str, Any]]:
14451455
combined_filter = f"{base_filter} and {filter}"
14461456
else:
14471457
combined_filter = base_filter
1448-
params = {"$filter": combined_filter}
1458+
params: Dict[str, str] = {"$filter": combined_filter}
1459+
if select:
1460+
params["$select"] = ",".join(select)
14491461
r = self._request("get", url, params=params)
14501462
return r.json().get("value", [])
14511463

src/PowerPlatform/Dataverse/operations/tables.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,12 @@ def get(self, table: str) -> Optional[Dict[str, Any]]:
179179

180180
# ------------------------------------------------------------------- list
181181

182-
def list(self, *, filter: Optional[str] = None) -> List[Dict[str, Any]]:
182+
def list(
183+
self,
184+
*,
185+
filter: Optional[str] = None,
186+
select: Optional[List[str]] = None,
187+
) -> List[Dict[str, Any]]:
183188
"""List all non-private tables in the Dataverse environment.
184189
185190
By default returns every table where ``IsPrivate eq false``. Supply
@@ -193,6 +198,13 @@ def list(self, *, filter: Optional[str] = None) -> List[Dict[str, Any]]:
193198
expressions must use the exact property names from the
194199
``EntityDefinitions`` metadata (typically PascalCase).
195200
:type filter: :class:`str` or None
201+
:param select: Optional list of property names to include in the
202+
response (projected via the OData ``$select`` query option).
203+
Property names must use the exact PascalCase names from the
204+
``EntityDefinitions`` metadata (e.g.
205+
``["LogicalName", "SchemaName", "DisplayName"]``).
206+
When ``None`` (the default), all properties are returned.
207+
:type select: :class:`list` of :class:`str` or None
196208
197209
:return: List of EntityDefinition metadata dictionaries.
198210
:rtype: :class:`list` of :class:`dict`
@@ -208,9 +220,14 @@ def list(self, *, filter: Optional[str] = None) -> List[Dict[str, Any]]:
208220
custom_tables = client.tables.list(
209221
filter="startswith(SchemaName, 'new_')"
210222
)
223+
224+
# List tables with only specific properties
225+
tables = client.tables.list(
226+
select=["LogicalName", "SchemaName", "EntitySetName"]
227+
)
211228
"""
212229
with self._client._scoped_odata() as od:
213-
return od._list_tables(filter=filter)
230+
return od._list_tables(filter=filter, select=select)
214231

215232
# ------------------------------------------------------------- add_columns
216233

tests/unit/data/test_odata_internal.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,69 @@ def test_returns_value_list(self):
179179
result = self.od._list_tables()
180180
self.assertEqual(result, expected)
181181

182+
def test_select_adds_query_param(self):
183+
"""_list_tables(select=...) adds $select as comma-joined string."""
184+
self._setup_response([])
185+
self.od._list_tables(select=["LogicalName", "SchemaName", "DisplayName"])
186+
187+
self.od._request.assert_called_once()
188+
call_kwargs = self.od._request.call_args
189+
params = call_kwargs.kwargs.get("params") or call_kwargs[1].get("params", {})
190+
self.assertEqual(params["$select"], "LogicalName,SchemaName,DisplayName")
191+
192+
def test_select_none_omits_query_param(self):
193+
"""_list_tables(select=None) does not add $select to params."""
194+
self._setup_response([])
195+
self.od._list_tables(select=None)
196+
197+
call_kwargs = self.od._request.call_args
198+
params = call_kwargs.kwargs.get("params") or call_kwargs[1].get("params", {})
199+
self.assertNotIn("$select", params)
200+
201+
def test_select_empty_list_omits_query_param(self):
202+
"""_list_tables(select=[]) does not add $select (empty list is falsy)."""
203+
self._setup_response([])
204+
self.od._list_tables(select=[])
205+
206+
call_kwargs = self.od._request.call_args
207+
params = call_kwargs.kwargs.get("params") or call_kwargs[1].get("params", {})
208+
self.assertNotIn("$select", params)
209+
210+
def test_select_preserves_case(self):
211+
"""_list_tables does not lowercase select values (PascalCase preserved)."""
212+
self._setup_response([])
213+
self.od._list_tables(select=["EntitySetName", "LogicalName"])
214+
215+
call_kwargs = self.od._request.call_args
216+
params = call_kwargs.kwargs.get("params") or call_kwargs[1].get("params", {})
217+
self.assertEqual(params["$select"], "EntitySetName,LogicalName")
218+
219+
def test_select_with_filter(self):
220+
"""_list_tables with both select and filter sends both params."""
221+
self._setup_response([{"LogicalName": "account"}])
222+
self.od._list_tables(
223+
filter="SchemaName eq 'Account'",
224+
select=["LogicalName", "SchemaName"],
225+
)
226+
227+
self.od._request.assert_called_once()
228+
call_kwargs = self.od._request.call_args
229+
params = call_kwargs.kwargs.get("params") or call_kwargs[1].get("params", {})
230+
self.assertEqual(
231+
params["$filter"],
232+
"IsPrivate eq false and SchemaName eq 'Account'",
233+
)
234+
self.assertEqual(params["$select"], "LogicalName,SchemaName")
235+
236+
def test_select_single_property(self):
237+
"""_list_tables(select=[...]) with a single property works correctly."""
238+
self._setup_response([])
239+
self.od._list_tables(select=["LogicalName"])
240+
241+
call_kwargs = self.od._request.call_args
242+
params = call_kwargs.kwargs.get("params") or call_kwargs[1].get("params", {})
243+
self.assertEqual(params["$select"], "LogicalName")
244+
182245

183246
class TestUpsert(unittest.TestCase):
184247
"""Unit tests for _ODataClient._upsert."""

tests/unit/test_tables_operations.py

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ def test_list(self):
100100

101101
result = self.client.tables.list()
102102

103-
self.client._odata._list_tables.assert_called_once_with(filter=None)
103+
self.client._odata._list_tables.assert_called_once_with(filter=None, select=None)
104104
self.assertIsInstance(result, list)
105105
self.assertEqual(result, expected_tables)
106106

@@ -113,7 +113,7 @@ def test_list_with_filter(self):
113113

114114
result = self.client.tables.list(filter="SchemaName eq 'Account'")
115115

116-
self.client._odata._list_tables.assert_called_once_with(filter="SchemaName eq 'Account'")
116+
self.client._odata._list_tables.assert_called_once_with(filter="SchemaName eq 'Account'", select=None)
117117
self.assertIsInstance(result, list)
118118
self.assertEqual(result, expected_tables)
119119

@@ -126,7 +126,52 @@ def test_list_with_filter_none_explicit(self):
126126

127127
result = self.client.tables.list(filter=None)
128128

129-
self.client._odata._list_tables.assert_called_once_with(filter=None)
129+
self.client._odata._list_tables.assert_called_once_with(filter=None, select=None)
130+
self.assertEqual(result, expected_tables)
131+
132+
def test_list_with_select(self):
133+
"""list(select=...) should pass the select list to _list_tables."""
134+
expected_tables = [
135+
{"LogicalName": "account", "SchemaName": "Account"},
136+
]
137+
self.client._odata._list_tables.return_value = expected_tables
138+
139+
result = self.client.tables.list(select=["LogicalName", "SchemaName", "EntitySetName"])
140+
141+
self.client._odata._list_tables.assert_called_once_with(
142+
filter=None,
143+
select=["LogicalName", "SchemaName", "EntitySetName"],
144+
)
145+
self.assertEqual(result, expected_tables)
146+
147+
def test_list_with_select_none_explicit(self):
148+
"""list(select=None) should behave identically to list() with no args."""
149+
expected_tables = [
150+
{"LogicalName": "account", "SchemaName": "Account"},
151+
]
152+
self.client._odata._list_tables.return_value = expected_tables
153+
154+
result = self.client.tables.list(select=None)
155+
156+
self.client._odata._list_tables.assert_called_once_with(filter=None, select=None)
157+
self.assertEqual(result, expected_tables)
158+
159+
def test_list_with_filter_and_select(self):
160+
"""list(filter=..., select=...) should pass both params to _list_tables."""
161+
expected_tables = [
162+
{"LogicalName": "account", "SchemaName": "Account"},
163+
]
164+
self.client._odata._list_tables.return_value = expected_tables
165+
166+
result = self.client.tables.list(
167+
filter="SchemaName eq 'Account'",
168+
select=["LogicalName", "SchemaName"],
169+
)
170+
171+
self.client._odata._list_tables.assert_called_once_with(
172+
filter="SchemaName eq 'Account'",
173+
select=["LogicalName", "SchemaName"],
174+
)
130175
self.assertEqual(result, expected_tables)
131176

132177
# ------------------------------------------------------------ add_columns

0 commit comments

Comments
 (0)