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
6 changes: 6 additions & 0 deletions .changes/unreleased/added-20260706-091126.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: added
body: Add new 'bulk-export' command for exporting workspace or folder items
time: 2026-07-06T09:11:26.916814199Z
custom:
Author: ohadedry
AuthorLink: https://github.com/ohadedry
109 changes: 109 additions & 0 deletions docs/commands/fs/bulk_export.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# `bulk-export` Command

Export a workspace or folder in bulk while preserving folder structure and item bindings.

The `bulk-export` command exports all supported items from a workspace or folder in a single bulk API request, preserving the folder hierarchy and item bindings (logical IDs) in the exported definitions.

!!! warning "When exporting items, the item definitions are exported without their sensitivity labels"

!!! note "Permissions"
- **Workspace export**: Only items that the caller has both read and write permissions for are exported.
Comment thread
ohadedry marked this conversation as resolved.
- **Folder export**: The caller must have read and write permissions for all folder items.
Comment thread
ohadedry marked this conversation as resolved.

!!! note "Beta API"
This command relies on the Fabric [bulk export item definitions API](https://learn.microsoft.com/en-us/rest/api/fabric/core/items/bulk-export-item-definitions(beta)), which is currently in beta.

!!! note "Differences from `export`"
- **Folder structure**: `bulk-export` preserves the workspace folder hierarchy in the output. `export` exports items flat.
- **Item bindings**: `bulk-export` preserves item logical IDs, enabling round-trip import/update. `export` uses nil UUIDs.
- **Format**: `bulk-export` does not support the `--format` flag. Items are exported in their default format only.
- **Target scope**: `bulk-export` operates on workspaces and folders. `export` operates on individual items or workspaces.

**Supported Targets:**

- `.Workspace` — exports all supported items in the workspace
- `.Folder` — exports all supported items in the folder and its sub-folders

**Supported Item Types:**

`CopyJob`, `CosmosDBDatabase`, `Dataflow`, `DataPipeline`, `DigitalTwinBuilder`, `DigitalTwinBuilderFlow`, `Environment`, `Eventhouse`, `Eventstream`, `GraphQLApi`, `GraphQuerySet`, `KQLDashboard`, `KQLDatabase`, `KQLQueryset`, `Lakehouse`, `Map`, `MirroredDatabase`, `MLExperiment`, `MLModel`, `MountedDataFactory`, `Notebook`, `Reflex`, `Report`, `SemanticModel`, `SparkJobDefinition`, `SQLDatabase`, `UserDataFunction`, `VariableLibrary`

Unsupported item types are automatically skipped and reported in the output summary.

**Usage:**

```
fab bulk-export <path> -o <output_path> --recursive [-f]
Comment thread
ohadedry marked this conversation as resolved.
```

**Parameters:**

| Parameter | Description |
|-----------|-------------|
| `<path>` | Path to the workspace or folder to export. |
| `-o, --output <output_path>` | Output directory path. Required. |
| `--recursive` | Recursively export folder contents. Required for workspace and folder targets. |
| `-f, --force` | Skip confirmation prompts. Exports without sensitivity label confirmation and proceeds when the output folder is not empty. Optional. |

**Examples:**

```bash
# Bulk-export an entire workspace to a local directory
fab bulk-export ws1.Workspace -o /tmp --recursive --force

# Bulk-export a specific folder
fab bulk-export ws1.Workspace/folder1.Folder -o /tmp --recursive --force
```

**Output:**

On success, the command prints a summary with the number of exported and skipped items:

```
Exported 15 items to '/tmp'. Skipped 1 items due to unsupported item types: Dashboard (1)
```

When using `--output_format json`, the output includes structured data:

```json
{
"exported": 15,
"exported_types": {
"SemanticModel": 5,
"Report": 4,
"Notebook": 3,
"DataPipeline": 3
},
"skipped": 1,
"skipped_types": {
"Dashboard": 1
},
"output": "/tmp"
}
```

**Output Directory Structure:**

The exported output mirrors the workspace folder structure:

```
<output_path>/
├── notebook1.Notebook/
│ ├── .platform
│ └── notebook-content.ipynb
├── Folder1/
│ ├── notebook2.Notebook/
│ │ ├── .platform
│ │ └── notebook-content.ipynb
│ └── report1.Report/
│ ├── .platform
│ ├── definition.pbir
│ └── StaticResources/
│ └── ...
└── Folder2/
└── model1.SemanticModel/
├── .platform
└── definition/
├── model.tmdl
└── ...
```
Comment thread
ohadedry marked this conversation as resolved.
1 change: 1 addition & 0 deletions docs/commands/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ It supports both Unix-style and Windows-style command names for file system oper
| Command | Description |
|---------|-------------|
| [`assign`](./fs/assign.md) | Assign a resource to a workspace |
| [`bulk-export`](./fs/bulk_export.md) | Export Folder or Workspace items in bulk |
| [`cd`](./fs/cd.md) | Change to the specified directory |
| [`cp` (copy)](./fs/cp.md) | Copy an item or file |
| [`export`](./fs/export.md) | Export an item |
Expand Down
15 changes: 15 additions & 0 deletions docs/examples/folder_examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,21 @@ fab rm ws1.Workspace/fd1.Folder

## Folder Operations

### Bulk-Export Folder Items

!!! info "When you execute the `bulk-export` command, the sensitivity labels are not part of the exported definitions"
Comment thread
ohadedry marked this conversation as resolved.

Bulk-export all supported items from a folder preserving folder structure and item bindings. See [bulk-export command](../commands/fs/bulk_export.md) for full details and supported item types.
Comment thread
ohadedry marked this conversation as resolved.

#### Bulk-Export a Folder Recursively

Export a folder and all its sub-folders to a local directory.

```
fab bulk-export ws1.Workspace/fd1.Folder -o /tmp --recursive
Comment thread
ohadedry marked this conversation as resolved.
```


### Copy Folder


Expand Down
12 changes: 12 additions & 0 deletions docs/examples/workspace_examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,18 @@ fab export ws1.Workspace -o /ws1.Workspace/lh1.Lakehouse/Files
fab export ws1.Workspace -o /ws1.Workspace/lh1.Lakehouse/Files -a
```

### Bulk-Export Workspace Items

!!! info "When you bulk-export item definitions, the sensitivity labels are not part of the definitions"

Bulk-export all supported items from a workspace preserving folder structure and item bindings. See [bulk-export command](../commands/fs/bulk_export.md) for full details and supported item types.

#### Bulk-Export Workspace to Local Directory

```
fab bulk-export ws1.Workspace -o /tmp --recursive
```

### Deploy Workspace Items

Deploy workspace items from local source to target workspaces using configuration files.
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ nav:
- api: commands/api/index.md
- assign: commands/fs/assign.md
- auth: commands/auth/index.md
- bulk-export: commands/fs/bulk_export.md
- cd: commands/fs/cd.md
- config: commands/config/index.md
- cp (copy): commands/fs/cp.md
Expand Down
6 changes: 5 additions & 1 deletion src/fabric_cli/client/fab_api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,11 @@ def do_request(
start_time = time.time()
response = session.request(method=method, url=url, **request_params)
fab_logger.log_debug_http_response(
response.status_code, response.headers, response.text, start_time
response.status_code,
response.headers,
response.text,
start_time,
ctxt_cmd,
Comment thread
ohadedry marked this conversation as resolved.
)

api_error_code = response.headers.get(
Expand Down
9 changes: 9 additions & 0 deletions src/fabric_cli/client/fab_api_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,15 @@ def get_item_definition(args: Namespace) -> ApiResponse:
return fabric_api.do_request(args)


def bulk_export_definitions(args: Namespace, payload: str) -> ApiResponse:
"""https://learn.microsoft.com/en-us/rest/api/fabric/core/items/bulk-export-item-definitions"""
args.uri = f"workspaces/{args.ws_id}/items/bulkExportDefinitions?beta=true"
args.method = "post"
args.wait = True

return fabric_api.do_request(args, data=payload)


def update_item_definition(
args: Namespace, payload: str, item_uri: Optional[bool] = False
) -> ApiResponse:
Expand Down
2 changes: 2 additions & 0 deletions src/fabric_cli/commands/fs/bulk_export/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

from argparse import Namespace
from copy import deepcopy
import json

from fabric_cli.core.hiearchy.fab_folder import Folder
from fabric_cli.core.fab_exceptions import FabricCLIError
from fabric_cli.core import fab_constant
from fabric_cli.client import fab_api_item as item_api
from fabric_cli.errors.bulk_export import BulkExportErrors
from fabric_cli.core.hiearchy.fab_hiearchy import Item
from fabric_cli.utils import fab_cmd_fs_utils as utils_fs
from fabric_cli.utils.fab_cmd_bulk_export_utils import ContextItemsSupportMap
from fabric_cli.utils import fab_cmd_bulk_export_utils as bulk_export_utils


def bulk_export_folder(context: Folder, args: Namespace) -> None:
"""Bulk-export a folder with its contents, including sub-folders recursively."""
args = deepcopy(args)

workspace_id = context.workspace.id
args.ws_id = workspace_id
args.from_path = context.path.strip("/")

items_support = _collect_context_items(context)
if not items_support["supported_items"]:
error_message = (
BulkExportErrors.empty_target(context.full_name)
if not items_support["unsupported_items"]
else BulkExportErrors.no_exportable_items()
)
raise FabricCLIError(
error_message,
fab_constant.ERROR_INVALID_OPERATION,
)

exported_item_ids = [item.id for item in items_support["supported_items"]]
payload = bulk_export_utils.create_bulk_export_payload(exported_item_ids)
response = item_api.bulk_export_definitions(args, payload)
exported_definitions = json.loads(response.text)
bulk_export_utils.export_definition_parts_to_storage(
args, context.full_name, exported_definitions
)
bulk_export_utils.print_bulk_export_summary(args, items_support)


def _collect_context_items(
context: Folder,
) -> ContextItemsSupportMap:
"""Recursively collect all item IDs under a folder and its sub-folders."""
supported_items: list[Item] = []
unsupported_items: list[Item] = []
elements = utils_fs.get_ws_elements(context)

for element in elements:
if isinstance(element, Item):
try:
if bulk_export_utils.is_command_supported(element):
supported_items.append(element)
except FabricCLIError:
unsupported_items.append(element)
Comment thread
ohadedry marked this conversation as resolved.
pass
elif isinstance(element, Folder):
folder_items = _collect_context_items(element)
supported_items.extend(folder_items["supported_items"])
unsupported_items.extend(folder_items["unsupported_items"])

return {"supported_items": supported_items, "unsupported_items": unsupported_items}
Loading
Loading