Skip to content
Open
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
6 changes: 6 additions & 0 deletions .changes/unreleased/added-20260512-150558.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: added
body: Return the job instance ID as a structured field in the JSON output of ``job run`` and ``job start`` commands
time: 2026-05-12T15:05:58.3242491+03:00
custom:
Author: shirasassoon
AuthorLink: https://github.com/shirasassoon
10 changes: 4 additions & 6 deletions src/fabric_cli/client/fab_api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@

from fabric_cli.client.fab_api_types import ApiResponse
from fabric_cli.core import fab_constant, fab_logger, fab_state_config

_HOST_APP_VERSION_RE = re.compile(r"\d+(\.\d+){0,2}(-[a-zA-Z0-9\.-]+)?")
from fabric_cli.core.fab_exceptions import (
AzureAPIError,
FabricAPIError,
Expand All @@ -29,6 +27,8 @@
from fabric_cli.utils import fab_ui as utils_ui
from fabric_cli.utils.fab_http_polling_utils import get_polling_interval

_HOST_APP_VERSION_RE = re.compile(r"\d+(\.\d+){0,2}(-[a-zA-Z0-9\.-]+)?")

GUID_PATTERN = r"([a-f0-9\-]{36})"
FABRIC_WORKSPACE_URI_PATTERN = rf"workspaces/{GUID_PATTERN}"

Expand All @@ -41,9 +41,7 @@ def _get_session() -> requests.Session:
global _shared_session
if _shared_session is None:
_shared_session = requests.Session()
retries = Retry(
total=3, backoff_factor=1, status_forcelist=[502, 503, 504]
)
retries = Retry(total=3, backoff_factor=1, status_forcelist=[502, 503, 504])
adapter = HTTPAdapter(max_retries=retries)
_shared_session.mount("https://", adapter)
_shared_session.headers.update({"Accept-Encoding": "gzip, deflate"})
Expand Down Expand Up @@ -103,7 +101,7 @@ def do_request(
request_params["continuationToken"] = continuation_token

# Build url
url = f"https://{url}/{uri}"
url = f"https://{url}/{uri.lstrip('/')}"
if request_params:
url += f"?{requests.compat.urlencode(request_params)}"

Expand Down
11 changes: 9 additions & 2 deletions src/fabric_cli/commands/jobs/fab_jobs_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def exec_command(args: Namespace, item: Item) -> None:
else:
payload = None

(response, job_instance_id) = jobs_api.run_on_demand_item_job(args, payload)
response, job_instance_id = jobs_api.run_on_demand_item_job(args, payload)

if response.status_code == 202:
if args.wait:
Expand Down Expand Up @@ -51,14 +51,21 @@ def exec_command(args: Namespace, item: Item) -> None:
args.instance_id = job_instance_id
response = jobs_api.cancel_item_job_instance(args)
if response.status_code == 202:
fab_ui.print_grey("")
Comment thread
shirasassoon marked this conversation as resolved.
fab_ui.print_output_format(
args,
message=f"Job instance '{args.instance_id}' cancelled (async)",
data={"id": args.instance_id},
Comment thread
shirasassoon marked this conversation as resolved.
show_key_value_list=True,
)
Comment thread
shirasassoon marked this conversation as resolved.
Comment thread
shirasassoon marked this conversation as resolved.
Comment thread
shirasassoon marked this conversation as resolved.

else:
fab_ui.print_grey("")
fab_ui.print_output_format(
args, message=f"Job instance '{job_instance_id}' created"
args,
message=f"Job instance '{job_instance_id}' created",
data={"id": job_instance_id},
show_key_value_list=True,
Comment thread
shirasassoon marked this conversation as resolved.
)
Comment thread
shirasassoon marked this conversation as resolved.
Comment thread
shirasassoon marked this conversation as resolved.
Comment thread
shirasassoon marked this conversation as resolved.
Comment thread
shirasassoon marked this conversation as resolved.
Comment thread
shirasassoon marked this conversation as resolved.
fab_ui.print_grey(
f"→ To see status run 'job run-status {item.path} --id {job_instance_id}'"
Expand Down
45 changes: 28 additions & 17 deletions src/fabric_cli/utils/fab_cmd_job_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@
from typing import Any, Optional

from requests.structures import CaseInsensitiveDict

from fabric_cli.client import fab_api_jobs as jobs_api
from fabric_cli.client.fab_api_types import ApiResponse
from fabric_cli.core import fab_constant, fab_logger
from fabric_cli.core.fab_exceptions import FabricCLIError
from fabric_cli.core.fab_types import FabricJobType
from fabric_cli.core.hiearchy.fab_hiearchy import Item
from fabric_cli.errors import ErrorMessages
from fabric_cli.utils.fab_http_polling_utils import get_polling_interval
from fabric_cli.utils import fab_ui
from fabric_cli.utils.fab_http_polling_utils import get_polling_interval


def add_item_props_to_args(args: Namespace, context: Item) -> None:
Expand Down Expand Up @@ -53,7 +54,7 @@ def wait_for_job_completion(
job_ins_id,
job_response: ApiResponse,
timeout: Optional[int] = None,
custom_polling_interval: Optional[int] = None
custom_polling_interval: Optional[int] = None,
) -> None:
args = Namespace(
ws_id=getattr(job_args, "ws_id", None),
Expand All @@ -70,8 +71,10 @@ def wait_for_job_completion(
status = "NotStarted"
content = None

initial_interval = get_polling_interval(job_response.headers, custom_polling_interval)
if (timeout is not None and timeout < initial_interval):
initial_interval = get_polling_interval(
job_response.headers, custom_polling_interval
)
if timeout is not None and timeout < initial_interval:
initial_interval = timeout
time.sleep(initial_interval)
total_wait_time = initial_interval
Expand All @@ -81,7 +84,7 @@ def wait_for_job_completion(
_t1 = time.time()
response = jobs_api.get_item_job_instance(args)
api_call_time = int(time.time() - _t1)

# Add API call time to total wait time
total_wait_time += api_call_time

Expand All @@ -92,9 +95,13 @@ def wait_for_job_completion(
# Available statuses are: NotStarted, InProgress, Completed, Deduped, Failed, Cancelled
if status in ["Completed", "Cancelled", "Deduped"]:
fab_ui.print_progress(f"Job instance status: {status}")
fab_ui.print_grey("")
if status == "Completed":
fab_ui.print_output_format(
args, message=f"Job instance '{job_ins_id}' completed"
args,
message=f"Job instance '{job_ins_id}' completed",
data={"id": job_ins_id},
show_key_value_list=True,
Comment thread
shirasassoon marked this conversation as resolved.
)
Comment thread
shirasassoon marked this conversation as resolved.
Comment thread
shirasassoon marked this conversation as resolved.
Comment thread
shirasassoon marked this conversation as resolved.
Comment thread
shirasassoon marked this conversation as resolved.
else:
fab_logger.log_warning(
Expand All @@ -104,14 +111,16 @@ def wait_for_job_completion(
elif status == "Failed":
fab_ui.print_entries_unix_style([content], content.keys(), header=True)
raise FabricCLIError(
ErrorMessages.Common.job_instance_failed(job_ins_id),
fab_constant.ERROR_JOB_FAILED,
)
ErrorMessages.Common.job_instance_failed(job_ins_id),
fab_constant.ERROR_JOB_FAILED,
)
elif status in ["NotStarted", "InProgress"]:
fab_ui.print_progress(f"Job instance status: {status}")

interval = get_polling_interval(response.headers, custom_polling_interval)


interval = get_polling_interval(
response.headers, custom_polling_interval
)

time.sleep(interval)
total_wait_time += interval

Expand All @@ -128,8 +137,6 @@ def wait_for_job_completion(
)




def _extract_times(times: str) -> list[str]:
# Extract the times from the input string
time_list = times.strip("[]").split(",")
Expand Down Expand Up @@ -420,9 +427,13 @@ def validate_timeout_polling_interval(args: Namespace) -> None:
"""Validate that polling interval is not greater than or equal to timeout."""
timeout = getattr(args, "timeout", None)
polling_interval = getattr(args, "polling_interval", None)

if timeout is not None and polling_interval is not None and polling_interval >= timeout:

if (
timeout is not None
and polling_interval is not None
and polling_interval >= timeout
):
raise FabricCLIError(
f"Custom polling interval ({polling_interval}s) cannot be greater than or equal to timeout ({timeout}s)",
fab_constant.ERROR_INVALID_INPUT,
)
)
Loading
Loading