Skip to content

Commit b69d8f2

Browse files
committed
more logicalName -> schemaName renaming
1 parent 7c2cd83 commit b69d8f2

6 files changed

Lines changed: 46 additions & 46 deletions

File tree

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,12 @@ The SDK provides a simple, pythonic interface for Dataverse operations:
9999
| Concept | Description |
100100
|---------|-------------|
101101
| **DataverseClient** | Main entry point for all operations with environment connection |
102-
| **Records** | Dataverse records represented as Python dictionaries with logical field names |
103-
| **Logical Names** | Use table logical names (`"account"`) and column logical names (`"name"`) |
102+
| **Records** | Dataverse records represented as Python dictionaries with column schema names |
103+
| **Schema Names** | Use table schema names (`"account"`, `"new_MyTestTable"`) and column schema names (`"name"`, `"new_MyTestColumn"`). See: [Table definitions in Microsoft Dataverse](https://learn.microsoft.com/en-us/power-apps/developer/data-platform/entity-metadata) |
104104
| **Bulk Operations** | Efficient bulk processing for multiple records with automatic optimization |
105105
| **Paging** | Automatic handling of large result sets with iterators |
106106
| **Structured Errors** | Detailed exception hierarchy with retry guidance and diagnostic information |
107-
| **Customization prefix values** | Custom tables and columns require a customization prefix value to be included for all operations (e.g., `"new_Title"`, not `"Title"`). See: [Table definitions in Microsoft Dataverse](https://learn.microsoft.com/en-us/power-apps/developer/data-platform/entity-metadata) |
107+
| **Customization prefix values** | Custom tables and columns require a customization prefix value to be included for all operations (e.g., `"new_MyTestTable"`, not `"MyTestTable"`). See: [Table definitions in Microsoft Dataverse](https://learn.microsoft.com/en-us/power-apps/developer/data-platform/entity-metadata) |
108108

109109
## Examples
110110

@@ -176,7 +176,7 @@ for record in results:
176176
print(record["name"])
177177

178178
# OData query with paging
179-
# Note: filter and expand parameters require exact casing
179+
# Note: filter and expand parameters are case sensitive
180180
pages = client.get(
181181
"account",
182182
select=["accountid", "name"], # select is case-insensitive (automatically lowercased)
@@ -244,7 +244,7 @@ client.delete_table("new_Product")
244244
```python
245245
# Upload a file to a record
246246
client.upload_file(
247-
logical_name="account",
247+
table_schema_name="account",
248248
record_id=account_id,
249249
file_name_attribute="new_document",
250250
path="/path/to/document.pdf"

examples/advanced/file_upload.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ def ensure_table():
187187
sys.exit(1)
188188

189189
entity_set = table_info.get("entity_set_name")
190-
table_schema_name = table_info.get("table_schema_name") or entity_set.rstrip("s")
190+
table_schema_name = table_info.get("table_schema_name")
191191
attr_prefix = table_schema_name.split('_',1)[0] if '_' in table_schema_name else table_schema_name
192192
name_attr = f"{attr_prefix}_name"
193193
small_file_attr_schema = f"{attr_prefix}_SmallDocument" # second file attribute for small single-request demo
@@ -264,7 +264,7 @@ def ensure_file_attribute_generic(schema_name: str, label: str, key_prefix: str)
264264
record_id = created_ids[0]
265265
else:
266266
raise RuntimeError("Unexpected create return; expected list[str] with at least one GUID")
267-
print({"record_created": True, "id": record_id, "logical": table_schema_name})
267+
print({"record_created": True, "id": record_id, "table schema name": table_schema_name})
268268
except Exception as e: # noqa: BLE001
269269
print({"record_created": False, "error": str(e)})
270270
sys.exit(1)

examples/basic/functional_testing.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,9 @@ def test_create_record(client: DataverseClient, table_info: Dict[str, Any]) -> s
123123
"""Test record creation."""
124124
print("\n📝 Record Creation Test")
125125
print("=" * 50)
126-
127-
logical_name = table_info.get("table_logical_name")
128-
attr_prefix = logical_name.split("_", 1)[0] if "_" in logical_name else logical_name
126+
127+
table_schema_name = table_info.get("table_schema_name")
128+
attr_prefix = table_schema_name.split("_", 1)[0] if "_" in table_schema_name else table_schema_name
129129

130130
# Create test record data
131131
test_data = {
@@ -139,7 +139,7 @@ def test_create_record(client: DataverseClient, table_info: Dict[str, Any]) -> s
139139

140140
try:
141141
print("🚀 Creating test record...")
142-
created_ids = client.create(logical_name, test_data)
142+
created_ids = client.create(table_schema_name, test_data)
143143

144144
if isinstance(created_ids, list) and created_ids:
145145
record_id = created_ids[0]
@@ -163,12 +163,12 @@ def test_read_record(client: DataverseClient, table_info: Dict[str, Any], record
163163
print("\n📖 Record Reading Test")
164164
print("=" * 50)
165165

166-
logical_name = table_info.get("table_logical_name")
167-
attr_prefix = logical_name.split("_", 1)[0] if "_" in logical_name else logical_name
166+
table_schema_name = table_info.get("table_schema_name")
167+
attr_prefix = table_schema_name.split("_", 1)[0] if "_" in table_schema_name else table_schema_name
168168

169169
try:
170170
print(f"🔍 Reading record: {record_id}")
171-
record = client.get(logical_name, record_id)
171+
record = client.get(table_schema_name, record_id)
172172

173173
if record:
174174
print("✅ Record retrieved successfully!")
@@ -198,15 +198,15 @@ def test_query_records(client: DataverseClient, table_info: Dict[str, Any]) -> N
198198
print("\n🔍 Record Query Test")
199199
print("=" * 50)
200200

201-
logical_name = table_info.get("table_logical_name")
202-
attr_prefix = logical_name.split("_", 1)[0] if "_" in logical_name else logical_name
201+
table_schema_name = table_info.get("table_schema_name")
202+
attr_prefix = table_schema_name.split("_", 1)[0] if "_" in table_schema_name else table_schema_name
203203

204204
try:
205205
print("🔍 Querying records from test table...")
206206

207207
# Query with filter and select
208208
records_iterator = client.get(
209-
logical_name,
209+
table_schema_name,
210210
select=[f"{attr_prefix}_name", f"{attr_prefix}_count", f"{attr_prefix}_amount"],
211211
filter=f"{attr_prefix}_is_active eq true",
212212
top=5,
@@ -234,14 +234,14 @@ def cleanup_test_data(client: DataverseClient, table_info: Dict[str, Any], recor
234234
print("\n🧹 Cleanup")
235235
print("=" * 50)
236236

237-
logical_name = table_info.get("table_logical_name")
237+
table_schema_name = table_info.get("table_schema_name")
238238

239239
# Ask user if they want to clean up
240240
cleanup_choice = input("Do you want to delete the test record? (y/N): ").strip().lower()
241241

242242
if cleanup_choice in ['y', 'yes']:
243243
try:
244-
client.delete(logical_name, record_id)
244+
client.delete(table_schema_name, record_id)
245245
print("✅ Test record deleted successfully")
246246
except Exception as e:
247247
print(f"⚠️ Failed to delete test record: {e}")

src/PowerPlatform/Dataverse/client.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ def create(self, table_schema_name: str, records: Union[Dict[str, Any], List[Dic
108108
:param table_schema_name: Table schema name (e.g. ``"account"``, ``"contact"``, or ``"new_customtable"``).
109109
:type table_schema_name: str
110110
:param records: A single record dictionary or a list of record dictionaries.
111-
Each dictionary should contain attribute logical names as keys.
111+
Each dictionary should contain attribute schema names as keys.
112112
:type records: dict or list[dict]
113113
114114
:return: List of created record GUIDs. Returns a single-element list for a single input.
@@ -273,7 +273,7 @@ def get(
273273
:type table_schema_name: str
274274
:param record_id: Optional GUID to fetch a specific record. If None, queries multiple records.
275275
:type record_id: str or None
276-
:param select: Optional list of attribute logical names to retrieve. Column names are
276+
:param select: Optional list of column schema names to retrieve. Column names are
277277
case-insensitive and automatically lowercased (e.g. ``["new_Title", "new_Amount"]``
278278
becomes ``"new_title,new_amount"``).
279279
:type select: list[str] or None
@@ -478,7 +478,7 @@ class ItemStatus(IntEnum):
478478
}
479479
480480
result = client.create_table("new_SampleItem", columns)
481-
print(f"Created table: {result['table_logical_name']}")
481+
print(f"Created table: {result['table_schema_name']}")
482482
print(f"Columns: {result['columns_created']}")
483483
484484
Create a table with a custom primary column name::

src/PowerPlatform/Dataverse/data/odata.py

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ def _create(self, entity_set: str, table_schema_name: str, record: Dict[str, Any
169169
table_schema_name : str
170170
Table schema name.
171171
record : dict[str, Any]
172-
Attribute payload mapped by logical column names.
172+
Attribute payload mapped by column schema names.
173173
174174
Returns
175175
-------
@@ -212,11 +212,11 @@ def _create_multiple(self, entity_set: str, table_schema_name: str, records: Lis
212212
table_schema_name : str
213213
Table schema name.
214214
records : list[dict[str, Any]]
215-
Payloads mapped by logical attribute names.
215+
Payloads mapped by column schema names.
216216
217217
Multi-create logical name resolution
218218
------------------------------------
219-
- If any payload omits ``@odata.type`` the client stamps ``Microsoft.Dynamics.CRM.<table_schema_name>``.
219+
- If any payload omits ``@odata.type`` the client stamps ``Microsoft.Dynamics.CRM.<table_logical_name>``.
220220
- If all payloads already include ``@odata.type`` no modification occurs.
221221
222222
Returns
@@ -666,22 +666,22 @@ def _extract_logical_table(sql: str) -> str:
666666
return m.group(1).lower()
667667

668668
# ---------------------- Entity set resolution -----------------------
669-
def _entity_set_from_schema_name(self, logical: str) -> str:
670-
"""Resolve entity set name (plural) from a logical (singular) name using metadata.
669+
def _entity_set_from_schema_name(self, table_schema_name: str) -> str:
670+
"""Resolve entity set name (plural) from a schema name (singular) name using metadata.
671671
672672
Caches results for subsequent queries. Case-insensitive.
673673
"""
674-
if not logical:
675-
raise ValueError("logical name required")
674+
if not table_schema_name:
675+
raise ValueError("table schema name required")
676676

677677
# Use normalized (lowercase) key for cache lookup
678-
cache_key = self._normalize_cache_key(logical)
678+
cache_key = self._normalize_cache_key(table_schema_name)
679679
cached = self._logical_to_entityset_cache.get(cache_key)
680680
if cached:
681681
return cached
682682
url = f"{self.api}/EntityDefinitions"
683683
# LogicalName in Dataverse is stored in lowercase, so we need to lowercase for the filter
684-
logical_lower = logical.lower()
684+
logical_lower = table_schema_name.lower()
685685
logical_escaped = self._escape_odata_quotes(logical_lower)
686686
params = {
687687
"$select": "LogicalName,EntitySetName,PrimaryIdAttribute",
@@ -694,16 +694,16 @@ def _entity_set_from_schema_name(self, logical: str) -> str:
694694
except ValueError:
695695
items = []
696696
if not items:
697-
plural_hint = " (did you pass a plural entity set name instead of the singular logical name?)" if logical.endswith("s") and not logical.endswith("ss") else ""
697+
plural_hint = " (did you pass a plural entity set name instead of the singular table schema name?)" if table_schema_name.endswith("s") and not table_schema_name.endswith("ss") else ""
698698
raise MetadataError(
699-
f"Unable to resolve entity set for logical name '{logical}'. Provide the singular logical name.{plural_hint}",
699+
f"Unable to resolve entity set for table schema name '{table_schema_name}'. Provide the singular table schema name.{plural_hint}",
700700
subcode=ec.METADATA_ENTITYSET_NOT_FOUND,
701701
)
702702
md = items[0]
703703
es = md.get("EntitySetName")
704704
if not es:
705705
raise MetadataError(
706-
f"Metadata response missing EntitySetName for logical '{logical}'.",
706+
f"Metadata response missing EntitySetName for table schema name '{table_schema_name}'.",
707707
subcode=ec.METADATA_ENTITYSET_NAME_MISSING,
708708
)
709709
self._logical_to_entityset_cache[cache_key] = es
@@ -730,20 +730,20 @@ def _to_pascal(self, name: str) -> str:
730730
parts = re.split(r"[^A-Za-z0-9]+", name)
731731
return "".join(p[:1].upper() + p[1:] for p in parts if p)
732732

733-
def _get_entity_by_logical_name(
733+
def _get_entity_by_table_schema_name(
734734
self,
735-
logical_name: str,
735+
table_schema_name: str,
736736
headers: Optional[Dict[str, str]] = None,
737737
) -> Optional[Dict[str, Any]]:
738-
"""Get entity metadata by LogicalName. Case-insensitive.
738+
"""Get entity metadata by table schema name. Case-insensitive.
739739
740740
Note: LogicalName is stored lowercase in Dataverse, so we lowercase the input
741741
for case-insensitive matching. The response includes SchemaName, LogicalName,
742742
EntitySetName, and MetadataId.
743743
"""
744744
url = f"{self.api}/EntityDefinitions"
745745
# LogicalName is stored lowercase, so we lowercase the input for lookup
746-
logical_lower = logical_name.lower()
746+
logical_lower = table_schema_name.lower()
747747
logical_escaped = self._escape_odata_quotes(logical_lower)
748748
params = {
749749
"$select": "MetadataId,LogicalName,SchemaName,EntitySetName",
@@ -777,7 +777,7 @@ def _create_entity(
777777
if solution_unique_name:
778778
params = {"SolutionUniqueName": solution_unique_name}
779779
self._request("post", url, json=payload, params=params)
780-
ent = self._get_entity_by_logical_name(
780+
ent = self._get_entity_by_table_schema_name(
781781
table_schema_name,
782782
headers={"Consistency": "Strong"},
783783
)
@@ -1171,7 +1171,7 @@ def _get_table_info(self, table_schema_name: str) -> Optional[Dict[str, Any]]:
11711171
dict | None
11721172
Metadata summary or ``None`` if not found.
11731173
"""
1174-
ent = self._get_entity_by_logical_name(table_schema_name)
1174+
ent = self._get_entity_by_table_schema_name(table_schema_name)
11751175
if not ent:
11761176
return None
11771177
return {
@@ -1193,7 +1193,7 @@ def _list_tables(self) -> List[Dict[str, Any]]:
11931193

11941194
def _delete_table(self, table_schema_name: str) -> None:
11951195
"""Delete a table by SchemaName. Case-insensitive."""
1196-
ent = self._get_entity_by_logical_name(table_schema_name)
1196+
ent = self._get_entity_by_table_schema_name(table_schema_name)
11971197
if not ent or not ent.get("MetadataId"):
11981198
raise MetadataError(
11991199
f"Table '{table_schema_name}' not found.",
@@ -1215,7 +1215,7 @@ def _create_table(
12151215
The server will determine the LogicalName automatically (usually lowercased SchemaName).
12161216
"""
12171217
# Check if table already exists (case-insensitive)
1218-
ent = self._get_entity_by_logical_name(table_schema_name)
1218+
ent = self._get_entity_by_table_schema_name(table_schema_name)
12191219
if ent:
12201220
raise MetadataError(
12211221
f"Table '{table_schema_name}' already exists.",
@@ -1270,7 +1270,7 @@ def _create_columns(
12701270
if not isinstance(columns, dict) or not columns:
12711271
raise TypeError("columns must be a non-empty dict[name -> type]")
12721272

1273-
ent = self._get_entity_by_logical_name(table_schema_name)
1273+
ent = self._get_entity_by_table_schema_name(table_schema_name)
12741274
if not ent or not ent.get("MetadataId"):
12751275
raise MetadataError(
12761276
f"Table '{table_schema_name}' not found.",
@@ -1316,7 +1316,7 @@ def _delete_columns(
13161316
if not isinstance(name, str) or not name.strip():
13171317
raise ValueError("column names must be non-empty strings")
13181318

1319-
ent = self._get_entity_by_logical_name(table_schema_name)
1319+
ent = self._get_entity_by_table_schema_name(table_schema_name)
13201320
if not ent or not ent.get("MetadataId"):
13211321
raise MetadataError(
13221322
f"Table '{table_schema_name}' not found.",

tests/unit/data/test_logical_crud.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class TestableClient(ODataClient):
3838
def __init__(self, responses):
3939
super().__init__(DummyAuth(), "https://org.example", None)
4040
self._http = DummyHTTPClient(responses)
41-
def _convert_labels_to_ints(self, logical_name, record): # pragma: no cover - test shim
41+
def _convert_labels_to_ints(self, table_schema_name, record): # pragma: no cover - test shim
4242
return record
4343

4444
# Helper metadata response for logical name resolution
@@ -116,7 +116,7 @@ def test_get_multiple_paging():
116116
assert pages == [[{"accountid": "1"}], [{"accountid": "2"}]]
117117

118118

119-
def test_unknown_logical_name_raises():
119+
def test_unknown_table_schema_name_raises():
120120
responses = [
121121
(200, {}, {"value": []}), # metadata lookup returns empty
122122
]

0 commit comments

Comments
 (0)