Metadata-Version: 2.4
Name: astrolabe-python-sdk
Version: 1.1.0
Summary: Python SDK for Astrolabe feature flag system
Author-email: Astrolabe Team <md.hawamdeh@gmail.com>
License: MIT
Project-URL: Homepage, https://github.com/astrolabe/astrolabe-python-sdk
Project-URL: Documentation, https://docs.astrolabe.dev/python-sdk
Project-URL: Repository, https://github.com/astrolabe/astrolabe-python-sdk
Project-URL: Bug Tracker, https://github.com/astrolabe/astrolabe-python-sdk/issues
Keywords: feature-flags,feature-toggles,configuration,sdk
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Systems Administration
Requires-Python: >=3.7
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.25.0
Provides-Extra: async
Requires-Dist: httpx>=0.24.0; extra == "async"
Provides-Extra: dev
Requires-Dist: pytest>=6.0; extra == "dev"
Requires-Dist: pytest-cov>=2.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
Requires-Dist: httpx>=0.24.0; extra == "dev"
Requires-Dist: black>=21.0; extra == "dev"
Requires-Dist: flake8>=3.8; extra == "dev"
Requires-Dist: mypy>=0.800; extra == "dev"
Dynamic: license-file

# Astrolabe Python SDK

A Python SDK for the Astrolabe feature flag system, supporting number, string, boolean, and JSON flags with environment-based configuration.

## Installation

```bash
pip install astrolabe-python-sdk
```

## Quick Start

```python
from astrolabe import AstrolabeClient

# Initialize the client with your environment
client = AstrolabeClient(env="development")  # or "staging", "production"

# Evaluate different types of flags
boolean_flag = client.get_boolean_flag("feature_enabled", default=False)
string_flag = client.get_string_flag("welcome_message", default="Hello!")
number_flag = client.get_number_flag("max_items", default=10)
json_flag = client.get_json_flag("config", default={"theme": "light"})

# Generic flag evaluation (type inferred from default)
flag_value = client.get_flag("any_flag", default="default_value")
```

## Usage

### Initialization

```python
from astrolabe import AstrolabeClient
from astrolabe.client import Environment

# Using string
client = AstrolabeClient("production")

# Using enum
client = AstrolabeClient(Environment.PRODUCTION)
```

### Flag Evaluation with Attributes

```python
# Pass attributes for context-aware flag evaluation
attributes = {
    "user_id": "12345",
    "region": "us-east-1",
    "plan": "premium"
}

result = client.get_boolean_flag(
    key="premium_feature", 
    default=False, 
    attributes=attributes
)
```

### Supported Flag Types

- **Boolean flags**: `get_boolean_flag(key, default, attributes=None)`
- **String flags**: `get_string_flag(key, default, attributes=None)`
- **Number flags**: `get_number_flag(key, default, attributes=None)` (supports int and float)
- **JSON flags**: `get_json_flag(key, default, attributes=None)` (returns dict)
- **Generic flags**: `get_flag(key, default, attributes=None)` (type inferred from default)

### Environments

Supported environments:
- `development`
- `staging` 
- `production`

## Async Support

For use in async frameworks (FastAPI, Starlette, aiohttp, etc.), install the async extra:

```bash
pip install astrolabe-python-sdk[async]
```

Then use `AsyncAstrolabeClient`, which uses `httpx.AsyncClient` under the hood and an `asyncio.Task` for background polling — it never blocks the event loop.

### Basic async usage

```python
import os
from astrolabe import AsyncAstrolabeClient

os.environ["ASTROLABE_API_URL"] = "https://your-astrolabe-api.com"

async def main():
    async with AsyncAstrolabeClient(
        env="production",
        subscribed_projects=["my-project"],
    ) as client:
        # Evaluate flags (sync — pure in-memory cache reads, no I/O)
        enabled = client.get_bool("my-project/dark-mode", default=False)
        limit  = client.get_number("my-project/rate-limit", default=100)
        banner = client.get_string("my-project/banner-text", default="Welcome!")
        config = client.get_json("my-project/ui-config", default={"theme": "light"})

        # With user attributes for rule-based targeting
        premium = client.get_bool(
            "my-project/premium-feature",
            default=False,
            attributes={"user_id": "u-42", "plan": "enterprise"},
        )

        # Manual refresh (non-blocking)
        await client.refresh_flags()
```

### FastAPI example

```python
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request
from astrolabe import AsyncAstrolabeClient

astrolabe: AsyncAstrolabeClient | None = None

@asynccontextmanager
async def lifespan(app: FastAPI):
    global astrolabe
    astrolabe = await AsyncAstrolabeClient(
        env="production",
        subscribed_projects=["backend"],
    ).start()
    yield
    await astrolabe.close()

app = FastAPI(lifespan=lifespan)

@app.get("/feature")
async def feature(request: Request):
    enabled = astrolabe.get_bool("backend/new-checkout", default=False)
    return {"new_checkout_enabled": enabled}
```

### FastAPI with `Depends()`

You can inject the client through FastAPI's dependency injection system:

```python
from contextlib import asynccontextmanager
from fastapi import Depends, FastAPI
from astrolabe import AsyncAstrolabeClient

@asynccontextmanager
async def lifespan(app: FastAPI):
    app.state.astrolabe = await AsyncAstrolabeClient(
        env="production",
        subscribed_projects=["backend"],
    ).start()
    yield
    await app.state.astrolabe.close()

app = FastAPI(lifespan=lifespan)


def get_astrolabe(request) -> AsyncAstrolabeClient:
    """Dependency that provides the shared AsyncAstrolabeClient."""
    return request.app.state.astrolabe


@app.get("/feature")
async def feature(flags: AsyncAstrolabeClient = Depends(get_astrolabe)):
    enabled = flags.get_bool("backend/new-checkout", default=False)
    limit = flags.get_number("backend/rate-limit", default=100)
    return {"new_checkout_enabled": enabled, "rate_limit": limit}


@app.get("/dashboard")
async def dashboard(flags: AsyncAstrolabeClient = Depends(get_astrolabe)):
    theme = flags.get_string("backend/theme", default="light")
    config = flags.get_json(
        "backend/dashboard-config",
        default={"widgets": []},
        attributes={"role": "admin"},
    )
    return {"theme": theme, "config": config}
```

This pattern stores the client on `app.state` and injects it via `Depends()`, so your route handlers stay testable — just override the dependency in tests.

### Explicit lifecycle (without context manager)

```python
client = AsyncAstrolabeClient("staging", subscribed_projects=["svc"])
await client.start()       # creates httpx client, fetches flags, starts polling

try:
    value = client.get_flag("svc/experiment", default="control")
finally:
    await client.close()   # stops polling, closes httpx client
```

## Development

This SDK is currently in development. Flag evaluation logic is stubbed and will be implemented in future versions.

## License

MIT License
