Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
c22f70e
fix: Add deltalake dependency and refactor schema extraction logic
pkontek Apr 30, 2026
c0dd71b
fix: Refactor `fab tables schema` to utilize `deltalake` library for …
pkontek Apr 30, 2026
f0327bc
fix: Correct typo in error constant for invalid Delta table
pkontek Apr 30, 2026
746e8f6
Remove unused import
pkontek Apr 30, 2026
ca07e17
fix: Replace TableNotFoundError with DeltaError for schema extraction…
pkontek May 7, 2026
0d18818
Merge branch 'tables_schema_fix' of https://github.com/SoletPL/fabric…
pkontek May 7, 2026
baaba5d
fix: Update schema extraction to use json.loads for DeltaTable schema
pkontek May 7, 2026
8221723
Potential fix for pull request finding
pkontek May 7, 2026
1012572
fix: Add unit tests for table schema command
pkontek May 7, 2026
3963efa
fix: Add deltalake dependency to development requirements
pkontek May 7, 2026
34c7141
rename test_tables to test_tables_schema
pkontek May 15, 2026
bce23dc
extract shared mock fixtures in test_tables_schema
pkontek May 15, 2026
a4c4ba7
combine delta error tests into parametrized test case
pkontek May 15, 2026
55c3515
Potential fix for pull request finding
pkontek May 15, 2026
f206e12
feat: add integration tests for table schema command and mock API res…
pkontek May 15, 2026
4cda700
fix: improve error handling in table schema extraction
pkontek May 15, 2026
c954a2a
fix: ensure access token is validated before schema extraction
pkontek May 17, 2026
0ba9da9
fix: correct URL formatting in do_request function
pkontek Jun 17, 2026
ed43c39
Merge branch 'main' into tables_schema_fix
pkontek Jun 17, 2026
c015864
fix: redact internal exception details from table schema error message
pkontek Jun 18, 2026
b6da0e7
refactor: extract DeltaTable construction into fab_delta_client
pkontek Jun 18, 2026
7996c2e
fix: use context.local_path instead of reconstructing table path in s…
pkontek Jun 18, 2026
6d4ac61
fix: strip .Shortcut suffix from table_local_path in add_table_props_…
pkontek Jun 18, 2026
ed998af
fix: reject item types without Delta-compatible Tables/ in schema com…
pkontek Jun 18, 2026
18df707
test: lock delta-rs schema serialisation contract for complex types
pkontek Jun 18, 2026
ceba9fc
fix: use ErrorMessages.Auth.access_token_failed() in fab_delta_client
pkontek Jun 18, 2026
ee79ee5
test: add checkpoint regression test for issue #228
pkontek Jun 18, 2026
d0a7e23
refactor: split test_tables_schema by taxonomy per test.instructions.md
pkontek Jun 18, 2026
82c732b
refactor: replace VCR cassette with pure dispatch test for table schema
pkontek Jun 18, 2026
b7d0831
test: add missing auth token None test for fab_delta_client
pkontek Jun 18, 2026
3761717
hore: tighten deltalake dependency to >=1.0.0,<2.0.0
pkontek Jun 18, 2026
df0360b
fix: strip .Shortcut from args.table_name in add_table_props_to_args
pkontek Jun 18, 2026
eebb6f2
style: apply black formatting to changed files
pkontek Jun 18, 2026
12bfa45
Fix deltalake version in requirements
pkontek Jun 18, 2026
0fca49f
removed the unused MagicMock import
pkontek Jun 19, 2026
93b4bef
Merge branch 'main' into tables_schema_fix
pkontek Jun 19, 2026
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
6 changes: 6 additions & 0 deletions .changes/unreleased/fixed-20260430-130558.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: fixed
body: Refactor `fab tables schema` to use the `deltalake` Python library for schema extraction via ABFSS URI instead of manually parsing Delta log commit files
time: 2026-04-30T13:05:58.364670843Z
custom:
Author: pkontek
AuthorLink: https://github.com/pkontek
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ dependencies = [
"requests",
"cryptography",
"fabric-cicd>=0.3.1",
"deltalake>=0.18.0",
Comment thread
pkontek marked this conversation as resolved.
Outdated
]
Comment thread
pkontek marked this conversation as resolved.

[project.scripts]
Expand Down
93 changes: 30 additions & 63 deletions src/fabric_cli/commands/tables/fab_tables_schema.py
Comment thread
pkontek marked this conversation as resolved.
Comment thread
pkontek marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -3,78 +3,45 @@

import json
Comment thread
pkontek marked this conversation as resolved.
Outdated
from argparse import Namespace
from typing import Optional

from fabric_cli.client import fab_api_onelake as onelake_api
from deltalake import DeltaTable
from deltalake.exceptions import TableNotFoundError

from fabric_cli.core import fab_constant
from fabric_cli.core import fab_handle_context as handle_context
from fabric_cli.core.fab_auth import FabAuth
from fabric_cli.core.fab_exceptions import FabricCLIError
from fabric_cli.core.hiearchy.fab_hiearchy import OneLakeItem
from fabric_cli.utils import fab_ui
from fabric_cli.utils import fab_util as utils


def exec_command(args: Namespace) -> None:
schema = _extract_schema_from_commit_logs(args)
if schema:
fab_ui.print_grey("Schema extracted successfully")
_schema = json.loads(schema)["fields"]
fab_ui.print_output_format(args, data=_schema, show_headers=True)
schema_fields = _get_table_schema(args)
fab_ui.print_grey("Schema extracted successfully")
fab_ui.print_output_format(args, data=schema_fields, show_headers=True)

Comment thread
pkontek marked this conversation as resolved.

def _get_table_schema(args: Namespace) -> list[dict]:
token = FabAuth().get_access_token(fab_constant.SCOPE_ONELAKE_DEFAULT)
if args.schema:
local_path = f"Tables/{args.schema}/{args.table_name}"
else:
local_path = f"Tables/{args.table_name}"

table_uri = (
f"abfss://{args.ws_id}@{fab_constant.API_ENDPOINT_ONELAKE}"
f"/{args.lakehouse_id}/{local_path}"
Comment thread
pkontek marked this conversation as resolved.
Outdated
)

try:
table = DeltaTable(
table_uri,
storage_options={
"bearer_token": token,
"use_fabric_endpoint": "true",
Comment thread
pkontek marked this conversation as resolved.
Outdated
},
)
return table.schema().json()["fields"]
Comment thread
pkontek marked this conversation as resolved.
Outdated
except TableNotFoundError:
raise FabricCLIError(
"Failed to extract the table schema. Please ensure the path points to a valid Delta table",
fab_constant.ERROR_INVALID_DETLA_TABLE,
fab_constant.ERROR_INVALID_DELTA_TABLE,
Comment thread
pkontek marked this conversation as resolved.
Outdated
)


def _get_commit_logs(args: Namespace) -> Optional[list[str]]:
_delta_log_path = args.path
_delta_log_path[-1] = _delta_log_path[-1] + "/_delta_log"

_context = handle_context.get_command_context(_delta_log_path, raise_error=True)
assert isinstance(_context, OneLakeItem)
onelake: OneLakeItem = _context
workspace_id = onelake.workspace.id
item_id = onelake.item.id
local_path = onelake.local_path

local_path = utils.remove_dot_suffix(local_path)
args.directory = f"{workspace_id}/?recursive=false&resource=filesystem&directory={item_id}/{local_path}&getShortcutMetadata=true"
response = onelake_api.list_tables_files_recursive(args)

if response.status_code in {200, 201}:
file_names = [f["name"] for f in response.json().get("paths", [])]
json_files = [
f"{workspace_id}/{item_id}/{f.split('/', 1)[1]}"
for f in file_names
if f.endswith(".json") and f != "_temporary"
]
json_files.sort(reverse=True)
return json_files
return None


def _extract_schema_from_commit_logs(args: Namespace) -> Optional[str]:
commit_logs = _get_commit_logs(args)

if not commit_logs:
return None

for log in commit_logs:
args.from_path = log
args.wait = True
response = onelake_api.read(args)

if response.status_code in {200, 201}:
json_string = response.text
json_objects = json_string.strip().split("\n")

for obj in json_objects:
commit_data = json.loads(obj)
if "metaData" in commit_data:
metadata = commit_data["metaData"]
schema = metadata["schemaString"]
Comment thread
pkontek marked this conversation as resolved.
return schema

return None
6 changes: 2 additions & 4 deletions src/fabric_cli/core/fab_constant.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@
)

API_ENDPOINT_POWER_BI = (
validate_and_get_env_variable(
"FAB_API_ENDPOINT_POWER_BI", "api.powerbi.com")
validate_and_get_env_variable("FAB_API_ENDPOINT_POWER_BI", "api.powerbi.com")
+ "/v1.0/myorg"
)

Expand Down Expand Up @@ -264,7 +263,7 @@
ERROR_INVALID_OPERATION = "InvalidOperation"
ERROR_INVALID_PATH = "InvalidPath"
ERROR_INVALID_PROPERTY = "InvalidProperty"
ERROR_INVALID_DETLA_TABLE = "InvalidDeltaTable"
ERROR_INVALID_DELTA_TABLE = "InvalidDeltaTable"

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is fab_constant considered a stable/public interface that external tooling imports directly? If so, a deprecated alias makes sense. If it's internal-only, the sweep was clean and no alias is needed.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ayeshurun, what do you think about that?

Comment thread
pkontek marked this conversation as resolved.
ERROR_INVALID_QUERY_FIELDS = "InvalidQueryFields"
ERROR_INVALID_WORKSPACE_TYPE = "InvalidWorkspaceType"
ERROR_INVALID_QUERY = "InvalidQuery"
Expand Down Expand Up @@ -351,4 +350,3 @@

# Invalid query parameters for set command across all fabric resources
SET_COMMAND_INVALID_QUERIES = ["id", "type", "workspaceId", "folderId"]

Loading