Metadata-Version: 2.4
Name: secploy
Version: 0.4.0
Summary: Event tracking and monitoring SDK for Python applications
Home-page: https://github.com/agastronics/secploy-python-sdk
Author: Agastronics
Author-email: support@secploy.com
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.25.0
Requires-Dist: pyyaml>=5.1
Requires-Dist: pydantic>=2.0.0
Requires-Dist: websocket-client>=1.9.0
Requires-Dist: psutil>=5.4.0
Requires-Dist: gputil>=1.4.0; extra == "realtime"
Provides-Extra: realtime
Requires-Dist: websocket-client>=1.9.0; extra == "realtime"
Requires-Dist: gputil>=1.4.0; extra == "realtime"
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: license-file
Dynamic: provides-extra
Dynamic: requires-dist
Dynamic: summary

# Secploy Python SDK

[![PyPI version](https://img.shields.io/pypi/v/secploy.svg)](https://pypi.org/project/secploy/)
[![Python versions](https://img.shields.io/pypi/pyversions/secploy.svg)](https://pypi.org/project/secploy/)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)

Secploy Python SDK provides event ingestion, structured log capture, and runtime configuration sync for Python services.

## Table Of Contents

- [Secploy Python SDK](#secploy-python-sdk)
  - [Table Of Contents](#table-of-contents)
  - [Highlights](#highlights)
  - [Installation](#installation)
  - [Quick Start](#quick-start)
  - [Runnable Example](#runnable-example)
  - [CLI](#cli)
  - [Framework Examples](#framework-examples)
    - [FastAPI](#fastapi)
    - [Flask](#flask)
    - [Django](#django)
  - [Configuration](#configuration)
    - [Supported Keys](#supported-keys)
    - [Environment Variables](#environment-variables)
  - [Runtime Config Access](#runtime-config-access)
    - [Realtime Config Updates](#realtime-config-updates)
    - [Manual Polling (Optional)](#manual-polling-optional)
  - [Structured Log Capture](#structured-log-capture)
  - [API Surface](#api-surface)
    - [SecployClient](#secployclient)
    - [ConfigManager](#configmanager)
  - [Production Notes](#production-notes)
  - [Troubleshooting](#troubleshooting)
    - [Missing dependency errors](#missing-dependency-errors)
    - [Config sync authentication failure (`401`)](#config-sync-authentication-failure-401)
    - [YAML output fails in CLI sync](#yaml-output-fails-in-cli-sync)
  - [License](#license)

## Highlights

- Send application events to Secploy ingest.
- Capture Python logs and uncaught exceptions with structured context.
- Pull project configs from the Secploy API.
- Receive config updates through WebSocket with automatic 15-second polling fallback.
- Bootstrap project config and local `.env` files from CLI.

## Installation

Install from PyPI:

```bash
pip install secploy
```

Install with explicit realtime extra:

```bash
pip install "secploy[realtime]"
```

## Quick Start

Create a `.secploy` file in your project root:

```yaml
api_key: YOUR_API_KEY
environment_key: YOUR_ENVIRONMENT_KEY
organization_id: YOUR_ORGANIZATION_ID
environment: production
```

Use the SDK:

```python
from secploy import SecployClient

client = SecployClient()

client.send_event(
    "user.signup",
    {
        "user_id": "u_123",
        "plan": "pro",
        "source": "landing_page",
    },
)

# Dot-access project configs
google_api_key = client.env.google_api_key
var_a = client.env.var_a

# Graceful shutdown
client.stop()
```

## Runnable Example

A complete runnable example is available at `examples/basic_usage.py`.

Run it from the repository root:

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

Or set explicit config file path:

```bash
python examples/basic_usage.py --config-file .secploy
```

## CLI

The package installs a `secploy` command.

Initialize project config:

```bash
secploy init
```

Force overwrite existing file:

```bash
secploy init --force
```

Sync remote configs to local file:

```bash
secploy sync --configs
```

Default output is `.env` in `KEY="value"` format.

Common options:

```bash
secploy sync --configs --output .env.local
secploy sync --configs --format json --output configs.json
secploy sync --configs --format yaml --output configs.yaml
secploy sync --configs --config-file /path/to/.secploy
```

## Framework Examples

### FastAPI

```python
from fastapi import FastAPI, Request
from secploy import SecployClient

app = FastAPI()
client = SecployClient()


@app.middleware("http")
async def secploy_http_events(request: Request, call_next):
    response = await call_next(request)
    client.send_event(
        "http.request",
        {
            "method": request.method,
            "path": request.url.path,
            "status_code": response.status_code,
        },
    )
    return response
```

### Flask

```python
from flask import Flask, request
from secploy import SecployClient

app = Flask(__name__)
client = SecployClient()


@app.after_request
def secploy_http_events(response):
    client.send_event(
        "http.request",
        {
            "method": request.method,
            "path": request.path,
            "status_code": response.status_code,
        },
    )
    return response
```

### Django

```python
from secploy import SecployClient

client = SecployClient()


class SecployEventMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        response = self.get_response(request)
        client.send_event(
            "http.request",
            {
                "method": request.method,
                "path": request.path,
                "status_code": response.status_code,
            },
        )
        return response
```

## Configuration

Configuration precedence (highest first):

1. Direct constructor arguments (`api_key`, `environment_key`, `organization_id`)
2. Environment variables (`SECPLOY_*`)
3. Config file (`.secploy`, `{project}.secploy`, or `*.secploy` discovered upward)
4. SDK defaults

### Supported Keys

| Key | Type | Default | Notes |
| --- | --- | --- | --- |
| `api_key` | `str` | Required | Secploy API key |
| `environment_key` | `str` | Required | Environment key |
| `organization_id` | `str` | Required | Organization identifier |
| `environment` | `str` | `development` | Environment label |
| `ingest_url` | `str` | `https://ingest.secploy.com` | Ingest base URL |
| `api_url` | `str` | `https://api.secploy.com` | API base URL |
| `heartbeat_interval` | `int` | `60` | Heartbeat interval |
| `max_retry` | `int` | `5` | Event processor retry cap |
| `sampling_rate` | `float` | `1.0` | Event sampling |
| `log_level` | `str` | `INFO` | Logger level |
| `batch_size` | `int` | `100` | Max events per batch |
| `max_queue_size` | `int` | `10000` | Queue size limit |
| `flush_interval` | `int` | `5` | Batch flush interval (seconds) |
| `retry_attempts` | `int` | `3` | Retry attempts |
| `ignore_errors` | `bool` | `true` | Continue on non-critical issues |
| `debug` | `bool` | `false` | Enables SDK debug logging |
| `source_root` | `str` | `None` | Optional source root metadata |
| `realtime` | `bool` | `true` | Enable/disable realtime config stream |

### Environment Variables

Use the `SECPLOY_` prefix with uppercased keys:

```bash
export SECPLOY_API_KEY=YOUR_API_KEY
export SECPLOY_ENVIRONMENT_KEY=YOUR_ENVIRONMENT_KEY
export SECPLOY_ORGANIZATION_ID=YOUR_ORGANIZATION_ID
export SECPLOY_ENVIRONMENT=production
export SECPLOY_DEBUG=false
```

## Runtime Config Access

`SecployClient` exposes a config manager at `client.configs`.

```python
client = SecployClient()

# Ergonomic dot-access (case-insensitive fallback)
google_api_key = client.env.google_api_key
var_a = client.env.var_a

# Lazy-fetch single value
api_token = client.configs.get("THIRD_PARTY_TOKEN")

# Get full snapshot
all_configs = client.configs.all()
```

For optional values, use:

```python
optional_value = client.env.get("missing_key", default="")
```

### Realtime Config Updates

By default, the client starts realtime config delivery from:

- `wss://<api-host>/ws/sdk/configs/` (derived from `api_url`)

Behavior:

- On websocket `config.update`, SDK refreshes config cache immediately.
- If websocket disconnects, SDK falls back to polling every 15 seconds.
- When websocket reconnects, polling fallback stops automatically.

Disable realtime in config:

```yaml
realtime: false
```

### Manual Polling (Optional)

```python
def on_change(key, old_value, new_value):
    print(f"Config changed: {key}: {old_value} -> {new_value}")


client.configs.start_refresh(interval=60, on_change=on_change)
# ...
client.configs.stop_refresh()
```

## Structured Log Capture

Capture root logger:

```python
client = SecployClient()
client.capture_logs()
```

Capture specific logger(s):

```python
client.capture_logs("my.service")
client.capture_logs(["uvicorn", "my.service"])
```

Stop capture:

```python
client.stop_capturing_logs("my.service")

# Stop all resources on shutdown
client.stop()
```

## Endpoint Blocking

Check if an endpoint is blocked before performing an action. This is useful for preventing actions on sensitive endpoints that have been administratively blocked.

### Basic Usage

```python
from secploy import SecployClient

client = SecployClient()

# Check if an endpoint is blocked
if client.endpoint_blocked(method='DELETE', endpoint='/api/users/123'):
    print("Cannot delete user - endpoint is blocked")
else:
    print("Safe to delete user")
```

### Authentication

No extra project identifier is required. The backend resolves the project from your SDK headers:

```yaml
api_key: YOUR_API_KEY
environment_key: YOUR_ENVIRONMENT_KEY
organization_id: YOUR_ORGANIZATION_ID
```

Then use the method without parameters:

```python
if client.endpoint_blocked(method='POST', endpoint='/api/billing/charge'):
    print("Billing endpoint is blocked")
```

### Safe Defaults

- Returns `False` if unable to determine block status (network error, missing config, etc.)
- Backend applies the blocked-endpoint rule matching for you
- All errors are logged but don't raise exceptions
- Uses your organization ID automatically from client configuration

## API Surface

### SecployClient

- `send_event(event_type: str, payload: dict) -> bool`
- `track_http_request(method: str, endpoint: str, status_code: int, message: str | None = None, context: dict | None = None) -> bool`
- `track_error(error: Exception, endpoint: str | None = None, method: str | None = None, status_code: int = 500, context: dict | None = None) -> bool`
- `track_metric(name: str, value: int | float, unit: str | None = None, tags: dict | None = None, context: dict | None = None, message: str | None = None) -> bool`
- `endpoint_blocked(method: str, endpoint: str) -> bool`
  - Check if an endpoint is blocked before performing an action
    - Queries the Secploy API for a server-side blocked-endpoint decision
  - Returns `True` if blocked, `False` if not blocked or on error (safe default)
    - Uses the client's configured `api_key`, `environment_key`, and `organization_id` headers
- `capture_logs(loggers: str | list[str] | None = None) -> None`
- `stop_capturing_logs(loggers: str | list[str] | None = None) -> None`
- `start() -> None`
- `stop() -> None`
- `env` (dot-access config proxy)
- `configs` (`ConfigManager`)

### ConfigManager

- `fetch() -> dict[str, str]`
- `get(key: str, default: str | None = None) -> str | None`
- `all() -> dict[str, str]`
- `start_refresh(interval: int = 60, on_change: callable | None = None) -> None`
- `stop_refresh() -> None`
- `start_realtime(ws_url: str, headers_callback: callable) -> None`
- `stop_realtime() -> None`

## Production Notes

- Reuse one `SecployClient` instance per service process.
- Ensure `client.stop()` is called during graceful shutdown.
- Keep `api_url` and `ingest_url` aligned with your Secploy environment.
- For containerized apps, mount `.secploy` via secret management or use environment variables.

## Troubleshooting

### Missing dependency errors

```bash
pip install -U secploy
```

### Config sync authentication failure (`401`)

Verify these values in `.secploy`:

- `api_key`
- `environment_key`
- `organization_id`

### YAML output fails in CLI sync

```bash
pip install pyyaml
```

## License

MIT
