Metadata-Version: 2.4
Name: ubti-fabric-accelerator
Version: 0.4.0
Summary: Python library for Fabric Accelerator artifact operations
License: Copyright (c) 2026 UBTI Inc.
        
        All rights reserved.
        
        This software and associated documentation files (the "Software") are
        proprietary to UBTI Inc. Internal use, modification, and distribution
        within UBTI Inc. and its authorized affiliates is permitted. No part of
        the Software may be distributed, sublicensed, or disclosed to any third
        party outside UBTI Inc. without prior written permission.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
        OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
        MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
        IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
        CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
        TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
        SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
        
Project-URL: Homepage, https://github.com/UBTI/fabric-accelerator
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.31
Requires-Dist: azure-identity>=1.15
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-cov>=5.0; extra == "dev"
Dynamic: license-file

# Fabric Accelerator Python Library

A standalone Python package that extracts Microsoft Fabric artifact
interaction into a reusable programmatic interface. This is the v0.1
initial trial: it deliberately excludes Fabric notebooks and pipelines
and exposes exactly three public capabilities.

## Public API

```python
import os
from fabric_accelerator import FabricArtifacts, ServicePrincipalCredential

credential = ServicePrincipalCredential(
    tenant_id=os.environ["FABRIC_TENANT_ID"],
    client_id=os.environ["FABRIC_CLIENT_ID"],
    client_secret=os.environ["FABRIC_CLIENT_SECRET"],
)

# Inside a Fabric notebook: workspace_id is auto-resolved from the
# notebook's own runtime context — you don't pass it.
client = FabricArtifacts(credential=credential)

# Outside Fabric (local dev, CI, or targeting a different workspace),
# pass it explicitly:
# client = FabricArtifacts(credential=credential, workspace_id=os.environ["FABRIC_WORKSPACE_ID"])

# Function 1 — discover artifacts in the workspace
artifacts = client.get_all_artifacts()

# Function 2 — resolves the artifact via get_all_artifacts(), then reads it
result = client.read_artifact("Customer_Lakehouse", path="Files/raw/customers.csv")

# Function 3 — resolves the artifact via get_all_artifacts(), then writes to it
status = client.write_artifact("Customer_Lakehouse", result.content, path="Files/out/copy.csv")
```

`get_all_artifacts()` is the single source of truth for artifact
discovery. `read_artifact()` and `write_artifact()` both resolve the
requested artifact through it rather than implementing a second,
independent lookup path.

### Workspace id: auto-detected inside Fabric, explicit outside it

`workspace_id` is now optional. When you omit it, `FabricArtifacts`
resolves the **current notebook's own workspace** automatically via
`notebookutils` (`runtime.context["currentWorkspaceId"]`, falling back
to `environment.workspaceId`). This means:

- Notebook code doesn't hard-code a workspace GUID that would silently
  go stale when the notebook is promoted from Dev → Test → Prod via a
  deployment pipeline — it always targets whichever workspace it's
  actually running in.
- Outside a Fabric notebook (local scripts, CI, or when you deliberately
  want to target a workspace *other than* the one you're running in),
  auto-detection isn't possible — pass `workspace_id=` explicitly, or
  the client raises `WorkspaceIdNotResolvableError` with a clear message.

## Using this library inside a Microsoft Fabric Notebook

The package already has the `src/` layout, `pyproject.toml`, and
`__init__.py` needed to build a wheel — you don't need to restructure
anything, just build and upload it.

### Step 1: Build the wheel

From the `fabric-accelerator/` folder (this repo's root, next to
`pyproject.toml`):

```bash
pip install build
python -m build
```

This creates `dist/fabric_accelerator-0.2.0-py3-none-any.whl` (and a
`.tar.gz` sdist, which you can ignore for Fabric).

### Step 2: Upload to Fabric

**Option A — Workspace Environment (recommended for reuse across notebooks):**

1. Go to your Fabric Workspace → **Workspace settings** → **Data
   Engineering/Science** → **Environments**.
2. Create a new environment or edit an existing one.
3. Go to **Libraries** → **Public Libraries** → **Add from file** →
   upload `fabric_accelerator-0.2.0-py3-none-any.whl`.
4. Also add `azure-identity` and `requests` as **PyPI public
   libraries** in the same environment (this library depends on them
   but the wheel itself doesn't bundle them — Fabric resolves public
   PyPI deps separately from custom wheels).
5. **Save** and **Publish** the environment, then attach it to your
   notebook (Notebook → Environment dropdown).

**Option B — Notebook-level (quick testing):**

1. Open your Fabric Notebook → **Resources** panel (paperclip icon) →
   upload `fabric_accelerator-0.2.0-py3-none-any.whl`.
2. In a cell:
   ```python
   %pip install /lakehouse/default/Files/fabric_accelerator-0.2.0-py3-none-any.whl
   %pip install azure-identity requests
   ```

### Step 3: Use it in the notebook

Store the service principal secret in **Azure Key Vault** and pull it
with `notebookutils.credentials.getSecret(...)` rather than pasting it
into the notebook — Fabric notebooks are often shared/exported, and a
hard-coded secret in a cell is a leak waiting to happen.

```python
from fabric_accelerator import FabricArtifacts, ServicePrincipalCredential

tenant_id = notebookutils.credentials.getSecret("https://<your-vault>.vault.azure.net/", "fabric-tenant-id")
client_id = notebookutils.credentials.getSecret("https://<your-vault>.vault.azure.net/", "fabric-client-id")
client_secret = notebookutils.credentials.getSecret("https://<your-vault>.vault.azure.net/", "fabric-client-secret")

credential = ServicePrincipalCredential(
    tenant_id=tenant_id,
    client_id=client_id,
    client_secret=client_secret,
)

# workspace_id omitted — auto-resolved from this notebook's own workspace.
client = FabricArtifacts(credential=credential)

artifacts = client.get_all_artifacts()
for a in artifacts:
    print(a.name, a.type.value)

result = client.read_artifact("Customer_Lakehouse", path="Files/raw/customers.csv")
status = client.write_artifact("Customer_Lakehouse", result.content, path="Files/out/copy.csv")
print(status.status)
```

If you genuinely need to hard-code values for a one-off test, use
`FABRIC_WORKSPACE_ID`/`FABRIC_TENANT_ID`/`FABRIC_CLIENT_ID`/`FABRIC_CLIENT_SECRET`
as Fabric notebook parameters or `os.environ` set via a prior cell —
never commit them into the notebook source.

### Updating the wheel later

Bump `version` in `pyproject.toml`, re-run `python -m build`, and
re-upload — Fabric environments version each library upload, so a
notebook pinned to an older environment publish won't silently pick up
the new wheel until you re-publish and re-attach.

## Installation (local development)

Requires Python 3.10+.

```bash
python -m venv .venv
source .venv/bin/activate        # Windows: .venv\Scripts\activate

pip install -r requirements.txt  # runtime deps only
# or, to also run the test suite:
pip install -r requirements-dev.txt

# then install this package itself (editable install)
pip install -e .
```

`requirements.txt` and `pyproject.toml` are kept in sync — either
installs the same runtime dependencies (`requests`, `azure-identity`).
Use whichever your workflow expects (`pip install -r requirements.txt`
for a plain venv, `pip install -e .` if you want the package on
`sys.path` as `fabric_accelerator`). You generally want both: the
`-e .` step is what makes `import fabric_accelerator` work.

## How to run

1. **Set the required environment variables** (see table below) —
   never hard-code these.
2. **Run the example end-to-end script** (this runs outside Fabric, so
   it passes `workspace_id` explicitly — see the notebook example below
   for the auto-detected version):

   ```bash
   python examples/basic_usage.py
   ```

   This lists every artifact in the workspace, reads a file from a
   Lakehouse named `Customer_Lakehouse`, and writes it back to a
   different path. Edit `examples/basic_usage.py` to point at your own
   artifact name and file paths.

3. **Or use it in your own script**:

   ```python
   import os
   from fabric_accelerator import FabricArtifacts, ServicePrincipalCredential

   credential = ServicePrincipalCredential(
       tenant_id=os.environ["FABRIC_TENANT_ID"],
       client_id=os.environ["FABRIC_CLIENT_ID"],
       client_secret=os.environ["FABRIC_CLIENT_SECRET"],
   )
   client = FabricArtifacts(credential=credential, workspace_id=os.environ["FABRIC_WORKSPACE_ID"])
   print(client.get_all_artifacts())
   ```

4. **Inside a Fabric notebook**, skip `FABRIC_WORKSPACE_ID` entirely —
   see [`examples/fabric_notebook_usage.py`](examples/fabric_notebook_usage.py)
   and the "Using this library inside a Microsoft Fabric Notebook"
   section below.

## Environment variables

Since you're authenticating with a **service principal**, set these
variables (e.g. in a local `.env` file that's **not** committed — see
`.gitignore` — or in your shell/CI secrets):

| Variable | Required | Description |
|---|---|---|
| `FABRIC_WORKSPACE_ID` | Only outside Fabric | GUID of the target Fabric workspace. Not needed inside a Fabric notebook — auto-resolved from the running notebook's own workspace. Required for local dev/CI, or to target a workspace other than the one you're running in. |
| `FABRIC_TENANT_ID` | Yes | Azure AD tenant id |
| `FABRIC_CLIENT_ID` | Yes | App registration (service principal) client id |
| `FABRIC_CLIENT_SECRET` | Yes | App registration client secret |

Example `.env` (load with `python-dotenv`, or `export $(cat .env | xargs)`,
or your shell's own env-file support — this package does not read
`.env` files itself, to avoid an extra dependency):

```bash
FABRIC_WORKSPACE_ID=00000000-0000-0000-0000-000000000000
FABRIC_TENANT_ID=00000000-0000-0000-0000-000000000000
FABRIC_CLIENT_ID=00000000-0000-0000-0000-000000000000
FABRIC_CLIENT_SECRET=your-client-secret-value
```

One-time setup your service principal needs on the Fabric side (not
part of this library, done in the Fabric/Power BI admin portal or
workspace settings):

- The app registration must be added to the target Fabric **workspace**
  as a member/contributor (Workspace access → Add people or groups →
  paste the service principal's app id).
- Tenant admin must have **service principal API access** enabled for
  Fabric (Admin portal → Tenant settings → Developer settings →
  "Service principals can use Fabric APIs").

### Credential providers available in the library

`ServicePrincipalCredential` is what you want given you only have a
service principal. The library also ships a couple of others in
`auth.py` for completeness / testing, but you don't need them:

| Provider | Use case |
|---|---|
| `ServicePrincipalCredential` | **Your case** — Azure AD app registration (tenant/client id + secret) |
| `StaticTokenCredential` | Unit tests, or a token already obtained elsewhere |
| `EnvironmentCredential` | Quick local hacking with a raw pre-fetched token (`FABRIC_ACCESS_TOKEN`) |
| `ManagedIdentityCredential` | Only relevant if hosted inside Azure with a managed identity |

You can also implement the `CredentialProvider` protocol yourself —
anything with a `get_token(scope) -> str` method works.

## Supported artifact types (v0.1)

Only **Lakehouse** read/write is implemented in this trial:

- `read_artifact(name)` with no `path` → returns Lakehouse item metadata.
- `read_artifact(name, path="Files/...")` → returns the file's raw bytes.
- `write_artifact(name, payload, path="Files/...")` → writes `bytes` or
  `str` content to the given file path.

`get_all_artifacts()` discovers and normalizes **all** item types in the
workspace (Lakehouse, Warehouse, Notebook, Pipeline, Report, etc.) so you
can see the full inventory — but `read_artifact` / `write_artifact`
against a non-Lakehouse artifact raise `UnsupportedArtifactTypeError`.

## Error handling

All exceptions are safe to log — they never include tokens, secrets, or
raw authorization headers.

| Exception | Raised when |
|---|---|
| `AuthenticationError` | Credentials/token cannot be obtained or are rejected |
| `ArtifactNotFoundError` | Requested artifact cannot be resolved |
| `ArtifactAmbiguousError` | More than one artifact matches the selector |
| `UnsupportedArtifactTypeError` | The trial does not support that artifact type |
| `ArtifactReadError` | Fabric read operation failed |
| `ArtifactWriteError` | Fabric write/update operation failed |
| `FabricAPIError` | Generic API failure (includes status code) |

## Testing

```bash
pip install -e ".[dev]"
pytest
```

Unit tests mock the HTTP layer entirely and require no live Fabric
workspace. An integration test against a real development workspace is
recommended separately (see the implementation guide, section 12) but
is intentionally not included in this repository since it requires live
credentials.

## Project structure

```
fabric-accelerator/
├── src/
│   └── fabric_accelerator/
│       ├── __init__.py     # public exports
│       ├── client.py       # FabricArtifacts client
│       ├── artifacts.py    # discovery, resolution, Lakehouse read/write
│       ├── auth.py         # credential providers
│       ├── models.py       # Artifact / ReadResult / WriteResult
│       ├── exceptions.py   # library exceptions
│       └── http.py         # HTTP client (headers, retries, error parsing)
├── tests/
├── examples/
│   └── basic_usage.py
├── pyproject.toml
├── README.md
├── LICENSE
└── .gitignore
```

## Limitations (v0.1)

- Only the Lakehouse artifact type supports read/write.
- Notebooks and pipelines are out of scope for this trial.
- No `create_artifact`, `delete_artifact`, or async API yet — see
  "Future evolution" below.

## Security

- Never hard-code client secrets, tokens or passwords.
- Use least-privilege Fabric permissions for the identity used by this
  library.
- All Fabric API calls use HTTPS.
- Authorization headers and raw sensitive payloads are never logged.
- Requests use bounded timeouts and a small number of controlled
  retries (429/5xx only, exponential backoff).

## Future evolution

`create_artifact()`, `delete_artifact()`, `update_artifact_metadata()`,
`list_artifact_versions()`, artifact-specific clients, workspace-level
helpers, async API support, a CLI, telemetry, and CI/CD publishing to a
package registry are planned for later trials without changing this
package's basic consumer experience.
