Skip to content

Commit 1a3a052

Browse files
committed
PR comment fixes
1 parent ebb5ee8 commit 1a3a052

2 files changed

Lines changed: 52 additions & 27 deletions

File tree

src/dataverse_sdk/data/odata.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,9 @@ def _logical_to_schema_name(self, logical_name: str) -> str:
715715
716716
Use this ONLY when creating new entities where we control the SchemaName.
717717
For existing entities, use _get_entity_schema_name() to get the actual SchemaName from server.
718+
719+
Note: All callers (_create_table, _create_columns) validate that logical_name contains
720+
an underscore before calling this method, enforcing Dataverse's publisher prefix requirement.
718721
"""
719722
# Normalize logical name first
720723
logical_name = self._normalize_logical_name(logical_name)
@@ -723,10 +726,7 @@ def _logical_to_schema_name(self, logical_name: str) -> str:
723726
if not logical_name:
724727
raise ValueError("logical_name cannot be empty or whitespace-only")
725728

726-
if "_" not in logical_name:
727-
# No prefix, just capitalize first letter
728-
return logical_name[:1].upper() + logical_name[1:]
729-
729+
# Split on first underscore to get prefix and name parts
730730
prefix, rest = logical_name.split("_", 1)
731731

732732
# Validate that rest is not empty (e.g., "new_" is invalid)
@@ -764,7 +764,7 @@ def _get_attribute_schema_name(self, entity_logical: str, attribute_logical: str
764764
return self._attribute_schema_cache[cache_key]
765765

766766
# Get entity metadata ID first
767-
ent = self._get_entity_by_logical(entity_logical)
767+
ent = self._get_entity_by_logical_name(entity_logical)
768768
if not ent or not ent.get("MetadataId"):
769769
raise MetadataError(
770770
f"Entity '{entity_logical}' not found.",
@@ -829,7 +829,7 @@ def _get_entity_by_schema(
829829
items = r.json().get("value", [])
830830
return items[0] if items else None
831831

832-
def _get_entity_by_logical(
832+
def _get_entity_by_logical_name(
833833
self,
834834
logical_name: str,
835835
headers: Optional[Dict[str, str]] = None,
@@ -874,7 +874,7 @@ def _create_entity(
874874
if solution_unique_name:
875875
params = {"SolutionUniqueName": solution_unique_name}
876876
self._request("post", url, json=payload, params=params)
877-
ent = self._get_entity_by_logical(
877+
ent = self._get_entity_by_logical_name(
878878
logical_name,
879879
headers={"Consistency": "Strong"},
880880
)
@@ -1280,7 +1280,7 @@ def _get_table_info(self, logical_name: str) -> Optional[Dict[str, Any]]:
12801280
# Normalize logical name for case-insensitive handling
12811281
logical_name = self._normalize_logical_name(logical_name)
12821282

1283-
ent = self._get_entity_by_logical(logical_name)
1283+
ent = self._get_entity_by_logical_name(logical_name)
12841284
if not ent:
12851285
return None
12861286
return {
@@ -1305,7 +1305,7 @@ def _delete_table(self, logical_name: str) -> None:
13051305
logical_name = self._normalize_logical_name(logical_name)
13061306

13071307
# Use strong consistency to ensure we get latest metadata after recent create
1308-
ent = self._get_entity_by_logical(logical_name, headers={"Consistency": "Strong"})
1308+
ent = self._get_entity_by_logical_name(logical_name, headers={"Consistency": "Strong"})
13091309
if not ent or not ent.get("MetadataId"):
13101310
raise MetadataError(
13111311
f"Table '{logical_name}' not found.",
@@ -1379,7 +1379,7 @@ def _create_columns(
13791379
# Normalize logical name for case-insensitive handling
13801380
logical_name = self._normalize_logical_name(logical_name)
13811381

1382-
ent = self._get_entity_by_logical(logical_name)
1382+
ent = self._get_entity_by_logical_name(logical_name)
13831383
if not ent or not ent.get("MetadataId"):
13841384
raise MetadataError(
13851385
f"Table '{logical_name}' not found.",
@@ -1393,6 +1393,11 @@ def _create_columns(
13931393
for column_name, column_type in columns.items():
13941394
# Normalize column logical name for case-insensitive handling
13951395
column_name_normalized = self._normalize_logical_name(column_name)
1396+
1397+
# Validate that column name includes publisher prefix
1398+
if "_" not in column_name_normalized:
1399+
raise ValueError(f"Column logical name must include publisher prefix (e.g., 'new_columnname'), got: '{column_name}'")
1400+
13961401
# Convert column logical name to SchemaName
13971402
col_schema = self._logical_to_schema_name(column_name_normalized)
13981403
payload = self._attribute_payload(col_schema, column_type)
@@ -1428,7 +1433,7 @@ def _delete_columns(
14281433
# Normalize logical name for case-insensitive handling
14291434
logical_name = self._normalize_logical_name(logical_name)
14301435

1431-
ent = self._get_entity_by_logical(logical_name)
1436+
ent = self._get_entity_by_logical_name(logical_name)
14321437
if not ent or not ent.get("MetadataId"):
14331438
raise MetadataError(
14341439
f"Table '{logical_name}' not found.",

tests/unit/data/test_naming_normalization.py

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,6 @@ def test_logical_to_schema_name_basic():
7676
assert c._logical_to_schema_name("abc_myentity") == "abc_Myentity"
7777

7878

79-
def test_logical_to_schema_name_no_underscore():
80-
"""Test SchemaName when no underscore (capitalize first letter)."""
81-
c = TestableClient([])
82-
assert c._logical_to_schema_name("account") == "Account"
83-
assert c._logical_to_schema_name("contact") == "Contact"
84-
85-
8679
# ============================================================================
8780
# Tests for _get_entity_schema_name
8881
# ============================================================================
@@ -114,7 +107,7 @@ def test_get_entity_schema_name_not_found():
114107
def test_get_attribute_schema_name_lookup():
115108
"""Test that _get_attribute_schema_name retrieves attribute SchemaName."""
116109
responses = [
117-
(200, {}, {"value": [MD_ENTITY_BY_LOGICAL]}), # _get_entity_by_logical uses EntityDefinitions endpoint
110+
(200, {}, {"value": [MD_ENTITY_BY_LOGICAL]}), # _get_entity_by_logical_name uses EntityDefinitions endpoint
118111
(200, {}, MD_ATTRIBUTE_TITLE) # Attribute lookup
119112
]
120113
c = TestableClient(responses)
@@ -306,14 +299,14 @@ def test_entity_set_from_logical_case_insensitive():
306299
assert len(c._http.calls) == 1
307300

308301

309-
def test_get_entity_by_logical_normalization():
310-
"""Test that _get_entity_by_logical normalizes input."""
302+
def test_get_entity_by_logical_name_normalization():
303+
"""Test that _get_entity_by_logical_name normalizes input."""
311304
responses = [
312305
(200, {}, {"value": [MD_ENTITY_BY_LOGICAL]})
313306
]
314307
c = TestableClient(responses)
315308

316-
entity = c._get_entity_by_logical("NEW_SAMPLEITEM")
309+
entity = c._get_entity_by_logical_name("NEW_SAMPLEITEM")
317310
assert entity["LogicalName"] == "new_sampleitem"
318311

319312
# Check that the query parameters used normalized name
@@ -638,11 +631,6 @@ def test_logical_to_schema_name_case_insensitive():
638631
assert c._logical_to_schema_name("NEW_SAMPLEITEM") == "new_Sampleitem"
639632
assert c._logical_to_schema_name("New_SampleItem") == "new_Sampleitem"
640633
assert c._logical_to_schema_name(" new_sampleitem ") == "new_Sampleitem"
641-
642-
# Test without prefix
643-
assert c._logical_to_schema_name("account") == "Account"
644-
assert c._logical_to_schema_name("ACCOUNT") == "Account"
645-
assert c._logical_to_schema_name("Account") == "Account"
646634

647635

648636
def test_logical_to_schema_name_edge_cases():
@@ -661,9 +649,41 @@ def test_logical_to_schema_name_edge_cases():
661649
with pytest.raises(ValueError, match="empty part after underscore"):
662650
c._logical_to_schema_name("new_")
663651

652+
# Name without underscore should raise ValueError (since split will fail)
653+
# This should never happen in practice because callers validate presence of underscore
654+
with pytest.raises(ValueError):
655+
c._logical_to_schema_name("account")
656+
664657
# Multiple underscores - only first split matters
665658
assert c._logical_to_schema_name("new_sample_item") == "new_Sample_item"
666659

667660

661+
def test_create_columns_requires_prefix():
662+
"""Test that _create_columns rejects column names without publisher prefix."""
663+
responses = [
664+
(200, {}, {"value": [MD_ENTITY_BY_LOGICAL]}), # Get entity by logical
665+
]
666+
c = TestableClient(responses)
667+
668+
# Column name without underscore should raise ValueError
669+
with pytest.raises(ValueError, match="Column logical name must include publisher prefix"):
670+
c._create_columns(
671+
logical_name="new_sampleitem",
672+
columns={"mycolumn": "string"} # Missing prefix
673+
)
674+
675+
676+
def test_create_table_requires_prefix():
677+
"""Test that _create_table rejects table names without publisher prefix."""
678+
c = TestableClient([])
679+
680+
# Table name without underscore should raise ValueError
681+
with pytest.raises(ValueError, match="Table logical name must include publisher prefix"):
682+
c._create_table(
683+
logical_name="mytable", # Missing prefix
684+
schema={"new_field": "string"}
685+
)
686+
687+
668688
if __name__ == "__main__":
669689
pytest.main([__file__, "-v"])

0 commit comments

Comments
 (0)