Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Auth:
| `delete` | `delete(logical_name, id)` | `None` | Delete one record. |
| `delete` | `delete(logical_name, list[id])` | `None` | Delete many (sequential). |
| `query_sql` | `query_sql(sql)` | `list[dict]` | Constrained read-only SELECT via `?sql=`. |
| `create_table` | `create_table(tablename, schema)` | `dict` | Creates custom table + columns. Friendly name (e.g. `SampleItem`) becomes schema `new_SampleItem`; explicit schema name (contains `_`) used as-is. |
| `create_table` | `create_table(tablename, schema, solution_unique_name=None)` | `dict` | Creates custom table + columns. Friendly name (e.g. `SampleItem`) becomes schema `new_SampleItem`; explicit schema name (contains `_`) used as-is. Pass `solution_unique_name` to attach the table to a specific solution instead of the default solution. |
| `create_column` | `create_column(tablename, columns)` | `list[str]` | Adds columns using a `{name: type}` mapping (same shape as `create_table` schema). Returns schema names for the created columns. |
| `get_table_info` | `get_table_info(schema_name)` | `dict | None` | Basic table metadata by schema name (e.g. `new_SampleItem`). Friendly names not auto-converted. |
| `list_tables` | `list_tables()` | `list[dict]` | Lists non-private tables. |
Expand Down Expand Up @@ -310,6 +310,7 @@ info = client.create_table(
"active": "bool",
"status": Status,
},
solution_unique_name="my_solution_unique_name", # optional: associate table with this solution
)

# Create or delete columns
Expand Down
16 changes: 14 additions & 2 deletions src/dataverse_sdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,12 @@ def get_table_info(self, tablename: str) -> Optional[Dict[str, Any]]:
"""
return self._get_odata()._get_table_info(tablename)

def create_table(self, tablename: str, schema: Dict[str, Any]) -> Dict[str, Any]:
def create_table(
self,
tablename: str,
schema: Dict[str, Any],
solution_unique_name: Optional[str] = None,
) -> Dict[str, Any]:
"""
Create a simple custom table with specified columns.

Expand All @@ -397,6 +402,9 @@ class ItemStatus(IntEnum):
}

:type schema: dict[str, Any]
:param solution_unique_name: Optional solution unique name that should own the new table.
When omitted the table is created in the default solution.
:type solution_unique_name: str or None

:return: Dictionary containing table metadata including ``entity_schema``,
``entity_set_name``, ``entity_logical_name``, ``metadata_id``, and ``columns_created``.
Expand Down Expand Up @@ -425,7 +433,11 @@ class ItemStatus(IntEnum):
print(f"Created table: {result['entity_logical_name']}")
print(f"Columns: {result['columns_created']}")
"""
return self._get_odata()._create_table(tablename, schema)
return self._get_odata()._create_table(
tablename,
schema,
solution_unique_name,
)

def delete_table(self, tablename: str) -> None:
"""
Expand Down
33 changes: 29 additions & 4 deletions src/dataverse_sdk/odata.py
Original file line number Diff line number Diff line change
Expand Up @@ -632,7 +632,13 @@ def _get_entity_by_schema(self, schema_name: str) -> Optional[Dict[str, Any]]:
items = r.json().get("value", [])
return items[0] if items else None

def _create_entity(self, schema_name: str, display_name: str, attributes: List[Dict[str, Any]]) -> str:
def _create_entity(
self,
schema_name: str,
display_name: str,
attributes: List[Dict[str, Any]],
solution_unique_name: Optional[str] = None,
) -> str:
url = f"{self.api}/EntityDefinitions"
payload = {
"@odata.type": "Microsoft.Dynamics.CRM.EntityMetadata",
Expand All @@ -646,7 +652,10 @@ def _create_entity(self, schema_name: str, display_name: str, attributes: List[D
"IsActivity": False,
"Attributes": attributes,
}
r = self._request("post", url, json=payload)
params = None
if solution_unique_name:
params = {"SolutionUniqueName": solution_unique_name}
self._request("post", url, json=payload, params=params)
ent = self._wait_for_entity_ready(schema_name)
if not ent or not ent.get("EntitySetName"):
raise RuntimeError(
Expand Down Expand Up @@ -1086,7 +1095,12 @@ def _delete_table(self, tablename: str) -> None:
url = f"{self.api}/EntityDefinitions({metadata_id})"
r = self._request("delete", url)

def _create_table(self, tablename: str, schema: Dict[str, Any]) -> Dict[str, Any]:
def _create_table(
self,
tablename: str,
schema: Dict[str, Any],
solution_unique_name: Optional[str] = None,
) -> Dict[str, Any]:
# Accept a friendly name and construct a default schema under 'new_'.
# If a full SchemaName is passed (contains '_'), use as-is.
entity_schema = self._normalize_entity_schema(tablename)
Expand All @@ -1110,7 +1124,18 @@ def _create_table(self, tablename: str, schema: Dict[str, Any]) -> Dict[str, Any
attributes.append(payload)
created_cols.append(attr_schema)

metadata_id = self._create_entity(entity_schema, tablename, attributes)
if solution_unique_name is not None:
if not isinstance(solution_unique_name, str):
raise TypeError("solution_unique_name must be a string when provided")
if not solution_unique_name:
raise ValueError("solution_unique_name cannot be empty")

metadata_id = self._create_entity(
entity_schema,
tablename,
attributes,
solution_unique_name,
)
ent2: Dict[str, Any] = self._wait_for_entity_ready(entity_schema) or {}
logical_name = ent2.get("LogicalName")

Expand Down
Loading