azpi 1.0.1
Azure Python Interface - A unified API client for Microsoft Azure and Fabric
License: MIT
Requires Python: >=3.9
Keywords: azure, fabric, microsoft, api, graph, teams, data-factory, spark, lakehouse, pipeline

# azpi - Azure Python Interface

[![PyPI version](https://badge.fury.io/py/azpi.svg)](https://badge.fury.io/py/azpi)
[![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Documentation](https://img.shields.io/badge/docs-mkdocs-blue)](https://fullsteam-corp-shared.github.io/pypi-pkg-azpi/)

A unified Python client library for Microsoft Azure and Fabric APIs. Works seamlessly both inside Microsoft Fabric notebooks and as a standalone library.

📚 **[Full Documentation](https://fullsteam-corp-shared.github.io/pypi-pkg-azpi/)** | 🚀 **[Quick Start Guide](https://fullsteam-corp-shared.github.io/pypi-pkg-azpi/getting-started/quickstart/)** | 📖 **[API Reference](https://fullsteam-corp-shared.github.io/pypi-pkg-azpi/api/fabric/)**

## Features

- **🔄 Automatic Environment Detection**: Automatically detects if running inside Fabric and uses `notebookutils` for authentication, or falls back to service principal auth for standalone usage.

- **📦 Microsoft Fabric API**
  - Workspace management (list, create, role assignments)
  - Pipeline execution and monitoring
  - Git synchronization (status, pull, push, connect)
  - Environment management with library publishing
  - Lakehouse, Notebook, and Environment resources
  - Connections with role-based access control
  - Capacity and tenant admin operations
  - OneLake data access roles (ABAC security)

- **📊 Microsoft Graph API**
  - Teams channel messaging
  - Adaptive Card support
  - User and group queries

- **🏭 Azure Resource Manager API**
  - Azure Data Factory pipeline execution
  - Resource group and factory management

- **🔐 Azure Key Vault**
  - Secret retrieval and management
  - Automatic auth handling in Fabric

- **🎴 Adaptive Cards**
  - Build rich, interactive cards
  - Send via Teams channels
  - Full element support (TextBlock, Image, Table, ColumnSet)

## Installation

```bash
pip install azpi
```

## Quick Start

### Inside Microsoft Fabric (Module-Level API)

When running inside a Fabric notebook, authentication is automatic:

```python
import azpi.fabric as fabric

# Automatically uses notebookutils for authentication
ws = fabric.workspace()  # Current workspace
print(f"Working in: {ws.name}")

# Run a pipeline
pipeline = ws.pipeline("Daily ETL")
job = pipeline.run()
job.wait()
print(f"Pipeline completed with status: {job.status}")
```

### Standalone Usage

For standalone usage, provide credentials:

```python
import azpi.fabric as fabric
from azpi import ServicePrincipalAuth

# Create auth and client
auth = ServicePrincipalAuth(
    tenant_id="your-tenant-id",
    client_id="your-client-id",
    client_secret="your-client-secret"
)
fabric.client(auth=auth)

# Or use environment variables (auto-detected)
# Set AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET
fabric.client()  # Auto-detects from env

ws = fabric.workspace("GENESIS_EXT")
lakehouse = ws.lakehouse("Analytics")
print(f"Lakehouse ID: {lakehouse.id}")
```

## Usage Examples

### Fabric Workspace Operations

```python
import azpi.fabric as fabric

# List all workspaces
workspaces = fabric.workspaces()
for ws in workspaces:
    print(f"  {ws.name}: {ws.id}")

# Access workspace by name or GUID
ws = fabric.workspace("GENESIS_EXT")

# List workspace items (all types or filtered)
items = ws.items(item_type="DataPipeline")  # Returns typed Pipeline objects
for item in items:
    print(f"  Pipeline: {item.display_name}")

# Convenience accessors
pipelines = ws.pipelines()       # Returns list[Pipeline]
lakehouses = ws.lakehouses()     # Returns list[Lakehouse]
notebooks = ws.notebooks()       # Returns list[Notebook]
environments = ws.environments() # Returns list[Environment]

# Create new items
new_pipeline = ws.pipelines.add("New ETL")
new_lakehouse = ws.lakehouses.add("New Data Lake")
```

### Git Synchronization

```python
import azpi.fabric as fabric

ws = fabric.workspace("GENESIS_EXT")

# Check Git status
git = ws.git
status = git.status()

print(f"Synced: {status.is_synced}")
print(f"Pending changes: {len(status.changes)}")

# Update from Git (pull)
if status.has_changes:
    operation = git.update_from_git()
    operation.wait(callback=lambda op: print(f"Progress: {op.percent_complete}%"))
    print("Sync complete!")
```

### Environment Library Management

```python
import azpi.fabric as fabric

ws = fabric.workspace()

# Access environment
env = ws.environment("ML Environment")

# Publish changes
env.publish()
```

### Dynamic API Access

The library supports dynamic endpoint access for undefined methods via `__getattr__`. This allows you to access new or less common API endpoints without waiting for explicit library support:

```python
import azpi.fabric as fabric

ws = fabric.workspace("GENESIS")

# Defined methods work as expected
pipeline = ws.pipeline("ETL_Pipeline")
lakehouse = ws.lakehouse("Analytics")

# Undefined methods fall through to generic API calls
# GET /workspaces/{id}/eventstreams
eventstreams = ws.eventstreams()

# GET /workspaces/{id}/kqlDatabases
kql_dbs = ws.kqlDatabases()

# GET /workspaces/{id}/warehouses
warehouses = ws.warehouses()
```

This same pattern works across modules:

```python
import azpi.datafactory as adf

# Defined
pipeline = adf.pipeline("ETL")

# Fallback for undefined endpoints
triggers = adf.triggers()  # List triggers
datasets = adf.datasets()  # List datasets
```

### Connections and Role Assignments

```python
import azpi.fabric as fabric

# List all connections
connections = fabric.connections()
for conn in connections:
    print(f"{conn.name}: {conn.connectivity_type}")

# Get specific connection
conn = fabric.connection("SQL Server")

# Manage role assignments
assignments = conn.roleAssignments()
for assignment in assignments:
    print(f"{assignment.principal_name}: {assignment.role}")

# Add role assignment
conn.roleAssignments.add(
    principalId="user-guid",
    principalType="User",
    role="Owner"
)

# Remove role assignment
conn.roleAssignments.delete(principal_id="user-guid")
```

### Capacity and Tenant Admin

```python
import azpi.fabric as fabric

# Get capacity
capacity = fabric.capacity("Premium Capacity")
print(f"Region: {capacity.region}")
print(f"State: {capacity.state}")

# List capacities
capacities = fabric.capacities()

# Tenant operations
tenant = fabric.tenant()
settings = tenant.settings()
```

### OneLake Data Access Roles (ABAC Security)

```python
import azpi.fabric as fabric
from azpi.fabric import Role, Rule, EntraMember, FabricItem, ReadWrite, ItemAccess

ws = fabric.workspace("Analytics")
lakehouse = ws.lakehouse("Sales Data")

# Define access rules with row/column-level security
from azpi.fabric import RowLevelSecurity

reader_role = Role(
    name="Regional Managers",
    rules=[
        Rule(
            path=["dbo.sales"],  # Specific table
            access=ReadWrite.READ,
            rls=RowLevelSecurity("region = 'West'")  # Row filter
        )
    ],
    members=[
        EntraMember("manager-group-guid")  # Entra ID group
    ]
)

# Apply role to lakehouse
lakehouse.accessRoles.add(reader_role)

# List existing roles
roles = lakehouse.accessRoles()
for role in roles:
    print(f"{role.name}: {len(role.rules)} rules")

# Grant access based on another item's permissions
data_engineer_role = Role(
    name="Data Engineers",
    rules=[
        Rule(path=["*"], access=ReadWrite.READWRITE)  # Full access
    ],
    members=[
        FabricItem(access=[ItemAccess.READ, ItemAccess.WRITE])
    ]
)
lakehouse.accessRoles.add(data_engineer_role)
```

### Teams Messaging

```python
import azpi.teams as teams
from azpi.cards import Adaptive, TextBlock, ColumnSet, Column, Image
from azpi import ServicePrincipalAuth

# Configure for standalone use
auth = ServicePrincipalAuth(
    tenant_id="...",
    client_id="...",
    client_secret="..."
)

teams.client(
    team_id="team-guid",
    channel_id="channel-guid",
    auth=auth
)

# Send simple message
teams.send("Hello from azpi!")

# Or get a channel object for multiple messages
channel = teams.channel("other-channel-id")
channel.send("First message")
channel.send("Second message")

# Send Adaptive Card
card = Adaptive(body=[
    ColumnSet(columns=[
        Column([Image("https://example.com/logo.png")], width="auto"),
        Column([
            TextBlock("Alert!", weight="Bolder", size="Large"),
            TextBlock("Something important happened")
        ])
    ])
])

channel.send(card)

# Or use a webhook URL directly
teams.send(card, webhook_url="https://outlook.office.com/webhook/...")
```

### Azure Data Factory

```python
import azpi.datafactory as adf
from azpi import ServicePrincipalAuth

auth = ServicePrincipalAuth(
    tenant_id="...",
    client_id="...",
    client_secret="..."
)

adf.client(
    subscription_id="...",
    resource_group="my-resource-group",
    factory="my-data-factory",
    auth=auth
)

# Execute ADF pipeline
pipeline = adf.pipeline("ETL_Pipeline")
job = pipeline.run(parameters={"date": "2025-01-01"})
job.wait(callback=lambda j: print(f"Status: {j.status}"))

# Or run directly
job = adf.run("ETL_Pipeline", date="2025-01-01")
job.wait()

# List all pipelines
for p in adf.pipelines():
    print(p.name)
```

### Key Vault Secrets

```python
from azpi import secrets
from azpi import ServicePrincipalAuth

# Inside Fabric, only vault_url needed
secrets.client(vault_url="https://my-vault.vault.azure.net/")

# Standalone: provide auth
auth = ServicePrincipalAuth(
    tenant_id="...",
    client_id="...",
    client_secret="..."
)
secrets.client(
    vault_url="https://my-vault.vault.azure.net/",
    auth=auth
)

# Read a secret
password = secrets.get("database-password")

# Write a secret (requires service principal permissions)
secrets.set("new-secret", "secret-value")

# List all secrets
for s in secrets.list():
    print(s['id'])
```

## Environment Detection

Check and use runtime context:

```python
from azpi import runtime

if runtime.isFabric:
    ctx = runtime.context
    print(f"Workspace: {ctx['workspace_name']}")
    print(f"Lakehouse: {ctx['lakehouse_name']}")
else:
    print("Running standalone")
```

## Authentication Methods

### 1. Automatic (Inside Fabric)
```python
import azpi.fabric as fabric
ws = fabric.workspace()  # Uses notebookutils automatically
```

### 2. Explicit Client
```python
import azpi.fabric as fabric
from azpi import ServicePrincipalAuth

auth = ServicePrincipalAuth(
    tenant_id="...",
    client_id="...",
    client_secret="..."
)
fabric.client(auth=auth)
```

### 3. Environment Variables
```bash
export AZURE_TENANT_ID="..."
export AZURE_CLIENT_ID="..."
export AZURE_CLIENT_SECRET="..."
```
```python
import azpi.fabric as fabric
ws = fabric.workspace("GENESIS")  # Auto-detects env vars
```

### 4. From Key Vault
```python
from azpi import secrets
import azpi.fabric as fabric
from azpi import ServicePrincipalAuth

# Setup secrets client (inside Fabric, only vault_url needed)
secrets.client(vault_url="https://my-vault.vault.azure.net/")

# Use secrets to build auth
auth = ServicePrincipalAuth(
    tenant_id="your-tenant-id",
    client_id=secrets.get("my-client-id-secret"),
    client_secret=secrets.get("my-client-secret-secret")
)
fabric.client(auth=auth)
```

### 5. Class-Based (Advanced)
```python
from azpi.fabric import FabricClient
from azpi import ServicePrincipalAuth

auth = ServicePrincipalAuth(
    tenant_id="...",
    client_id="...",
    client_secret="..."
)

client = FabricClient(auth=auth)
ws = client.workspace("GENESIS")
```

## API Reference

### Core
- `runtime.isFabric` - Check if running in Fabric (property)
- `runtime.context` - Get Fabric runtime context (property)
- `runtime.sempy` - Access sempy.fabric module if available (property)
- `runtime.notebookutils` - Access notebookutils module if available (property)

### azpi.secrets Module
- `client(vault_url, auth=None)` - Create/configure client
- `get(key)` - Get a secret
- `set(key, value)` - Set a secret
- `list()` - List all secrets
- `vault()` - Get configured vault URL
- `reset()` - Clear cached client

### azpi.fabric Module

**Configuration:**
- `client(auth=None)` - Create/configure client
- `reset()` - Clear cached client

**Workspaces:**
- `workspace(name_or_id=None)` - Get workspace (current if None)
- `workspaces()` - List all workspaces
- `workspaces.add(name, capacity_id=None)` - Create workspace

**Connections:**
- `connection(name_or_id)` - Get connection
- `connections()` - List all connections
- `connections.add(name, type, connectivity)` - Create connection

**Admin:**
- `capacity(name_or_id)` - Get capacity
- `capacities()` - List all capacities
- `tenant()` - Get tenant admin interface

**Workspace Methods:**
- `.pipeline(name_or_id)` - Get pipeline
- `.pipelines()` - List pipelines
- `.pipelines.add(name)` - Create pipeline
- `.lakehouse(name_or_id)` - Get lakehouse
- `.lakehouses()` - List lakehouses
- `.lakehouses.add(name)` - Create lakehouse
- `.notebook(name_or_id)` - Get notebook
- `.notebooks()` - List notebooks
- `.notebooks.add(name)` - Create notebook
- `.environment(name_or_id)` - Get environment
- `.environments()` - List environments
- `.environments.add(name)` - Create environment
- `.items(item_type=None)` - List items (typed objects)
- `.git` - Git integration interface
- `.roleAssignments()` - List workspace role assignments
- `.roleAssignments.add(principalId, principalType, role)` - Add role assignment
- `.roleAssignments.delete(principal_id)` - Remove role assignment

**Lakehouse Methods:**
- `.accessRoles()` - List data access roles
- `.accessRoles.add(role)` - Add/update role
- `.accessRoles.replace(roles)` - Replace all roles

**Connection Methods:**
- `.roleAssignments()` - List connection role assignments
- `.roleAssignments.add(principalId, principalType, role)` - Add role assignment
- `.roleAssignments.delete(principal_id)` - Remove role assignment

**Git:**
- `.status()` - Get sync status
- `.updateFromGit()` - Pull from Git
- `.commitToGit(comment)` - Push to Git

**OneLake Classes:**
- `Role` - Data access role with rules and members
- `Rule` - Path-based access rule with optional RLS/CLS
- `EntraMember` - Grant access to Entra ID principals
- `FabricItem` - Grant access based on item permissions
- `RowLevelSecurity` / `ColumnLevelSecurity` - Query constraints

### azpi.teams Module
- `client(team_id, channel_id, auth=None)` - Create/configure client
- `send(message, subject=None)` - Send message to default channel
- `channel(channel_id)` - Get channel object
- `channels()` - List channels in team
- Dynamic endpoint access via `__getattr__` for undefined methods

### azpi.datafactory Module
- `client(subscription_id, resource_group, factory, auth=None)` - Create/configure client
- `pipeline(name)` - Get pipeline
- `pipelines()` - List pipelines
- `run(pipeline_name, **params)` - Run pipeline directly
- Dynamic endpoint access via `__getattr__` for undefined methods

## Requirements

- Python 3.9+
- `requests` library
- Inside Fabric: `notebookutils` and `sempy.fabric` (pre-installed)

## License

MIT License - see LICENSE file for details.