Metadata-Version: 2.4
Name: subscription-sdk
Version: 1.0.0
Summary: Python Client SDK for Subscription Platform
License: MIT
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: urllib3>=2.0.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.20.0; extra == "dev"
Requires-Dist: responses>=0.23.0; extra == "dev"
Requires-Dist: flask>=2.0.0; extra == "dev"
Requires-Dist: fastapi>=0.80.0; extra == "dev"
Requires-Dist: httpx>=0.24.0; extra == "dev"
Requires-Dist: uvicorn>=0.15.0; extra == "dev"

# subscription-sdk-python

Python SDK for the Subscription and Entitlements Platform.

## Installation

```bash
pip install subscription-sdk
```

## Middleware Adapters

### Flask

```python
from flask import Flask, request, jsonify
from subscription_sdk.adapters.flask_adapter import subscription_flask

app = Flask(__name__)

# Apply Flask Middleware
subscription_flask(
    app,
    api_key="sub_live_...",
    base_url="https://api.infosub.io",
    get_org_id=lambda: request.headers.get("X-Org-Id")
)

@app.route("/api/v1/employees", methods=["GET"])
def get_employees():
    return jsonify({"data": []}), 200
```

### FastAPI

```python
from fastapi import FastAPI, Request
from subscription_sdk.adapters.fastapi_adapter import SubscriptionMiddleware

app = FastAPI()

# Apply FastAPI Middleware
app.add_middleware(
    SubscriptionMiddleware,
    api_key="sub_live_...",
    base_url="https://api.infosub.io",
    get_org_id=lambda req: req.headers.get("x-org-id")
)

@app.get("/api/v1/employees")
def get_employees(request: Request):
    return {"data": []}
```

## Manual Client Usage

```python
from subscription_sdk import SubscriptionClient

client = SubscriptionClient(api_key="sub_live_...", base_url="https://api.infosub.io")
client.start()  # Starts background poller thread

mapping = client.match_route("GET", "/api/v1/employees")
if mapping:
    check = client.check_entitlement(
        organization_external_id="tenant-acme",
        resource_mapping_id=mapping.id,
        quantity=1.0
    )
    if check.allowed:
        try:
            # Execute business logic...
            
            client.commit_reservation(check.reservationId)
        except Exception:
            client.cancel_reservation(check.reservationId)
```

Alternatively, you can simplify the manual transaction pattern using `.execute(...)`, which handles the check-reserve-commit-cancel flow automatically.

**Basic Example:**
```python
# The execute method takes a callback function to run
result = client.execute(
    organization_id="tenant-acme",
    resource_mapping_id=mapping.id,
    callback=lambda: save_employee(employee),
    quantity=1.0
)
```

**Flask / FastAPI Route Handler Example:**
```python
@app.route("/employees-manual", methods=["POST"])
def create_employee_manual():
    org_id = request.headers.get("X-Org-Id") or request.args.get("orgId")
    mapping = client.match_route("POST", "/employees")
    
    if not mapping:
        return jsonify({"error": "Mapping not found"}), 500
        
    try:
        result = client.execute(
            organization_id=org_id,
            resource_mapping_id=mapping.id,
            quantity=1.0,
            callback=lambda: {
                "status": "success",
                "message": "Employee created successfully via manual execute wrapper."
            }
        )
        return jsonify(result), 200
    except PermissionError as err:
        # Throws PermissionError if entitlement check is denied (check.allowed is False)
        return jsonify({"status": "error", "message": str(err)}), 403
    except Exception as err:
        return jsonify({"status": "error", "message": str(err)}), 500
```

