# Malti Python SDK
[](https://pypi.org/project/malti-telemetry/)
[](https://pypi.org/project/malti-telemetry/)
[](https://opensource.org/licenses/MIT)
A Python library for collecting and sending telemetry data to Malti server using any Starlette-compatible framework.
## Features
- π **High Performance**: Asynchronous batch processing with connection pooling
- π **Thread-Safe**: Designed for multi-worker applications
- π― **Clean Mode**: Automatically filters out bot traffic (401/404 responses)
- π **Multi-Framework**: Works with any Starlette-compatible framework (FastAPI, Starlette, Responder, etc.)
- π **Rich Telemetry**: Collects method, endpoint, status, response time, consumer, and context
- π **Automatic Batching**: Efficient batching with overflow protection
- β‘ **Non-Blocking**: Telemetry collection doesn't impact request performance
- π‘οΈ **Retry Logic**: Exponential backoff for failed requests
- ποΈ **Configurable**: Extensive environment variable configuration
- π§ **Framework Optimized**: Enhanced integrations for popular frameworks
- π **IP Consumer Extraction**: Optional IP address extraction from X-Forwarded-For with anonymization
## Installation
```bash
pip install malti-telemetry
```
## Quick Start
### FastAPI Integration
```python
from fastapi import FastAPI
from malti_telemetry.middleware import MaltiMiddleware
app = FastAPI()
# Add telemetry middleware (route patterns automatically extracted!)
app.add_middleware(MaltiMiddleware)
@app.get("/users/{user_id}")
async def get_user(user_id: int):
return {"user_id": user_id, "name": "John Doe"}
# Recorded as: method=GET, endpoint="/users/{user_id}"
if __name__ == "__main__":
import uvicorn
uvicorn.run(app)
```
### Starlette Integration
```python
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.responses import JSONResponse
from malti_telemetry.middleware import MaltiMiddleware
app = Starlette()
# Add telemetry middleware (lifespan auto-injected!)
app.add_middleware(Middleware(MaltiMiddleware))
@app.route("/users/{user_id}")
async def get_user(request):
user_id = request.path_params["user_id"]
return JSONResponse({"user_id": user_id, "name": "John Doe"})
# Recorded as: method=GET, endpoint="/users/{user_id}"
if __name__ == "__main__":
import uvicorn
uvicorn.run(app)
```
### Responder Integration
```python
from responder import API
from malti_telemetry.middleware import MaltiMiddleware
api = API()
# Add telemetry middleware (generic Starlette middleware works with Responder)
api.add_middleware(MaltiMiddleware)
@api.route("/users/{user_id}")
async def get_user(req, resp, *, user_id):
resp.media = {"user_id": user_id, "name": "John Doe"}
```
### Generic Starlette Middleware
```python
from starlette.applications import Starlette
from starlette.middleware import Middleware
from malti_telemetry.middleware import MaltiMiddleware
app = Starlette()
# Add telemetry middleware (works with any Starlette framework)
app.add_middleware(Middleware(MaltiMiddleware))
@app.route("/api/data")
async def get_data(request):
return JSONResponse({"data": "example"})
```
### Environment Configuration
Set these environment variables before starting your application:
```bash
export MALTI_API_KEY="your-api-key-here"
export MALTI_SERVICE_NAME="my-fastapi-app"
export MALTI_URL="https://your-malti-server.muzy.dev"
export MALTI_NODE="production-node-1"
# Optional: Enable IP address consumer extraction
export MALTI_USE_IP_AS_CONSUMER=true
export MALTI_IP_ANONYMIZE=true
```
## Configuration
### Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `MALTI_API_KEY` | *(required)* | Your Malti API key |
| `MALTI_SERVICE_NAME` | `"unknown-service"` | Name of your service |
| `MALTI_URL` | `"http://localhost:8000"` | Malti server URL |
| `MALTI_NODE` | `"unknown-node"` | Node identifier |
| `MALTI_BATCH_SIZE` | `500` | Records per batch |
| `MALTI_BATCH_INTERVAL` | `60.0` | Seconds between batch sends |
| `MALTI_MAX_RETRIES` | `3` | Max retry attempts |
| `MALTI_RETRY_DELAY` | `1.0` | Base retry delay (seconds) |
| `MALTI_HTTP_TIMEOUT` | `30.0` | HTTP request timeout |
| `MALTI_MAX_KEEPALIVE_CONNECTIONS` | `5` | Max keepalive connections |
| `MALTI_MAX_CONNECTIONS` | `10` | Max total connections |
| `MALTI_OVERFLOW_THRESHOLD_PERCENT` | `90.0` | Buffer overflow threshold |
| `MALTI_CLEAN_MODE` | `true` | Ignore 401/404 responses |
| `MALTI_USE_IP_AS_CONSUMER` | `false` | Use IP address as consumer fallback |
| `MALTI_IP_ANONYMIZE` | `false` | Anonymize IP addresses (simple octet masking) |
### Programmatic Configuration
```python
from malti_telemetry import configure_malti
configure_malti(
service_name="my-service",
api_key="your-api-key",
malti_url="https://api.malti.muzy.dev",
node="prod-web-01",
batch_size=1000,
clean_mode=True,
use_ip_as_consumer=True,
ip_anonymize=True
)
```
## Advanced Usage
### Framework-Specific Features
#### FastAPI Features
**Route Pattern Extraction**: Automatic conversion of actual paths to route patterns:
- `/users/123` β `/users/{user_id}`
- `/api/v1/posts/456/comments` β `/api/v1/posts/{post_id}/comments`
- Works with nested routes and mount points
**Context Information**: Add context using FastAPI's request state:
```python
from fastapi import Request
@app.route("/users/{user_id}")
async def get_user(request):
user_id = request.path_params["user_id"]
if user_id < 1000:
request.state.context = "legacy"
else:
request.state.context = "current"
return JSONResponse({"user_id": user_id, "name": "John Doe"})
```
#### Starlette Features
**Automatic Lifespan Management**: Telemetry system starts/stops automatically:
```python
from starlette.applications import Starlette
from starlette.middleware import Middleware
from malti_telemetry.middleware import MaltiMiddleware
app = Starlette()
app.add_middleware(Middleware(MaltiMiddleware)) # Lifespan auto-injected!
# No need for manual lifespan management!
```
**Route Pattern Extraction**: Automatically extracts route patterns from Starlette routing:
```python
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.routing import Route, Mount
from malti_telemetry.middleware import MaltiMiddleware
app = Starlette()
app.add_middleware(Middleware(MaltiMiddleware))
async def user_handler(request):
return JSONResponse({"user_id": request.path_params["user_id"]})
async def post_handler(request):
return JSONResponse({"post_id": request.path_params["post_id"]})
# Works with all Starlette routing patterns
app.routes = [
Route("/api/v1/users/{user_id}", endpoint=user_handler),
Mount("/api/v2", routes=[
Route("/posts/{post_id}", endpoint=post_handler),
]),
]
# Automatically recorded as:
# method=GET, endpoint="/api/v1/users/{user_id}"
# method=GET, endpoint="/api/v2/posts/{post_id}"
```
### Consumer Identification
Malti automatically extracts consumer information from headers:
1. `x-consumer-id` header
2. `x-user-id` header
3. `consumer-id` header
4. `user-id` header
**IP Address as Consumer**: When no consumer headers are found, you can configure Malti to use IP addresses as a fallback:
```python
# Enable IP address extraction from X-Forwarded-For header
configure_malti(
use_ip_as_consumer=True,
ip_anonymize=True # Optional: anonymize IPs for privacy
)
# Or via environment variables
export MALTI_USE_IP_AS_CONSUMER=true
export MALTI_IP_ANONYMIZE=true
```
**IP Anonymization**: When enabled, IP addresses are anonymized using simple octet masking:
- IPv4: `192.168.1.100` β `192.168.1.xxx`
- IPv6: `2001:db8:85a3:8d3:1319:8a2e:370:7348` β `2001:db8:85a3:8d3:xxxx:xxxx:xxxx:xxxx`
**IP Extraction Priority**:
1. X-Forwarded-For header (uses first/leftmost IP)
2. Direct client IP (fallback)
**Custom Consumer Extraction**: Set consumer information in your framework:
```python
# FastAPI
@app.middleware("http")
async def set_consumer(request: Request, call_next):
request.state.malti_consumer = "app"
return await call_next(request)
# Starlette
@app.middleware("http")
async def set_consumer(request, call_next):
# Add consumer to ASGI scope
request.scope["state"]["malti_consumer"] = "api"
response = await call_next(request)
return response
```
### Manual Telemetry Recording
```python
from malti_telemetry import get_telemetry_system
telemetry = get_telemetry_system()
# Record a custom event
telemetry.record_request(
method="GET",
endpoint="/api/custom",
status=200,
response_time=150,
consumer="custom-client",
context="manual-recording"
)
```
### Statistics and Monitoring
```python
from malti_telemetry import get_malti_stats
stats = get_malti_stats()
print(stats)
# {
# 'total_added': 1250,
# 'total_sent': 1200,
# 'total_failed': 50,
# 'current_size': 50,
# 'max_size': 25000,
# 'service_name': 'my-service',
# 'running': True
# }
```
## Supported Frameworks
Malti Telemetry works with any Starlette-compatible framework:
- **FastAPI**: Enhanced route pattern extraction and request.state integration
- **Starlette**: Base middleware with full functionality and lifespan management
- **Responder**: Works with generic Starlette middleware
- **Any ASGI framework**: Generic middleware for custom implementations
### Architecture
#### Core Components
1. **TelemetryCollector**: Collects HTTP request telemetry data
2. **BatchSender**: Sends batched telemetry data to Malti server
3. **TelemetrySystem**: Combines collector and sender with unified interface
4. **TelemetryBuffer**: Thread-safe buffer for storing records
### Worker Process Model
Each FastAPI/Uvicorn worker process gets its own telemetry system instance:
```
βββββββββββββββββββ βββββββββββββββββββ
β Worker Process β β Worker Process β
β βββββββββββββββ β β βββββββββββββββ β
β βTelemetrySys β β β βTelemetrySys β β
β β βββββββββββ β β β β βββββββββββ β β
β β βBuffer β β β β β βBuffer β β β
β β βββββββββββ β β β β βββββββββββ β β
β β βββββββββββ β β β β βββββββββββ β β
β β βSender β β β β β βSender β β β
β β βββββββββββ β β β β βββββββββββ β β
β βββββββββββββββ β β βββββββββββββββ β
βββββββββββββββββββ βββββββββββββββββββ
```
## Development
### Setup
```bash
git clone https://github.com/muzy/malti-telemetry.git
cd python/
pip install -e ".[dev]"
```
### Testing
```bash
pytest
```
### Code Quality
```bash
black malti_telemetry/
isort malti_telemetry/
mypy malti_telemetry/
flake8 malti_telemetry/
```
## License
MIT License - see [LICENSE](LICENSE) file for details.
Raw data
{
"_id": null,
"home_page": null,
"name": "malti-telemetry",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.11",
"maintainer_email": "smuzy <malti@muzy.dev>",
"keywords": "telemetry, monitoring, fastapi, analytics, api, self-hosted, starlette",
"author": null,
"author_email": null,
"download_url": "https://files.pythonhosted.org/packages/ca/89/81d70e54fc2657183f6bd5ec0e4b4436158d559373ec09c84b23d4ee6301/malti_telemetry-1.0.4.tar.gz",
"platform": null,
"description": "# Malti Python SDK\n\n[](https://pypi.org/project/malti-telemetry/)\n[](https://pypi.org/project/malti-telemetry/)\n[](https://opensource.org/licenses/MIT)\n\nA Python library for collecting and sending telemetry data to Malti server using any Starlette-compatible framework.\n\n## Features\n\n- \ud83d\ude80 **High Performance**: Asynchronous batch processing with connection pooling\n- \ud83d\udd12 **Thread-Safe**: Designed for multi-worker applications\n- \ud83c\udfaf **Clean Mode**: Automatically filters out bot traffic (401/404 responses)\n- \ud83c\udf1f **Multi-Framework**: Works with any Starlette-compatible framework (FastAPI, Starlette, Responder, etc.)\n- \ud83d\udcca **Rich Telemetry**: Collects method, endpoint, status, response time, consumer, and context\n- \ud83d\udd04 **Automatic Batching**: Efficient batching with overflow protection\n- \u26a1 **Non-Blocking**: Telemetry collection doesn't impact request performance\n- \ud83d\udee1\ufe0f **Retry Logic**: Exponential backoff for failed requests\n- \ud83c\udf9b\ufe0f **Configurable**: Extensive environment variable configuration\n- \ud83d\udd27 **Framework Optimized**: Enhanced integrations for popular frameworks\n- \ud83c\udf10 **IP Consumer Extraction**: Optional IP address extraction from X-Forwarded-For with anonymization\n\n## Installation\n\n```bash\npip install malti-telemetry\n```\n\n## Quick Start\n\n### FastAPI Integration\n\n```python\nfrom fastapi import FastAPI\nfrom malti_telemetry.middleware import MaltiMiddleware\n\napp = FastAPI()\n\n# Add telemetry middleware (route patterns automatically extracted!)\napp.add_middleware(MaltiMiddleware)\n\n@app.get(\"/users/{user_id}\")\nasync def get_user(user_id: int):\n return {\"user_id\": user_id, \"name\": \"John Doe\"}\n\n# Recorded as: method=GET, endpoint=\"/users/{user_id}\"\n\nif __name__ == \"__main__\":\n import uvicorn\n uvicorn.run(app)\n```\n\n### Starlette Integration\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.middleware import Middleware\nfrom starlette.responses import JSONResponse\nfrom malti_telemetry.middleware import MaltiMiddleware\n\napp = Starlette()\n\n# Add telemetry middleware (lifespan auto-injected!)\napp.add_middleware(Middleware(MaltiMiddleware))\n\n@app.route(\"/users/{user_id}\")\nasync def get_user(request):\n user_id = request.path_params[\"user_id\"]\n return JSONResponse({\"user_id\": user_id, \"name\": \"John Doe\"})\n\n# Recorded as: method=GET, endpoint=\"/users/{user_id}\"\n\nif __name__ == \"__main__\":\n import uvicorn\n uvicorn.run(app)\n```\n\n### Responder Integration\n\n```python\nfrom responder import API\nfrom malti_telemetry.middleware import MaltiMiddleware\n\napi = API()\n\n# Add telemetry middleware (generic Starlette middleware works with Responder)\napi.add_middleware(MaltiMiddleware)\n\n@api.route(\"/users/{user_id}\")\nasync def get_user(req, resp, *, user_id):\n resp.media = {\"user_id\": user_id, \"name\": \"John Doe\"}\n```\n\n### Generic Starlette Middleware\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.middleware import Middleware\nfrom malti_telemetry.middleware import MaltiMiddleware\n\napp = Starlette()\n\n# Add telemetry middleware (works with any Starlette framework)\napp.add_middleware(Middleware(MaltiMiddleware))\n\n@app.route(\"/api/data\")\nasync def get_data(request):\n return JSONResponse({\"data\": \"example\"})\n```\n\n### Environment Configuration\n\nSet these environment variables before starting your application:\n\n```bash\nexport MALTI_API_KEY=\"your-api-key-here\"\nexport MALTI_SERVICE_NAME=\"my-fastapi-app\"\nexport MALTI_URL=\"https://your-malti-server.muzy.dev\"\nexport MALTI_NODE=\"production-node-1\"\n\n# Optional: Enable IP address consumer extraction\nexport MALTI_USE_IP_AS_CONSUMER=true\nexport MALTI_IP_ANONYMIZE=true\n```\n\n## Configuration\n\n### Environment Variables\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `MALTI_API_KEY` | *(required)* | Your Malti API key |\n| `MALTI_SERVICE_NAME` | `\"unknown-service\"` | Name of your service |\n| `MALTI_URL` | `\"http://localhost:8000\"` | Malti server URL |\n| `MALTI_NODE` | `\"unknown-node\"` | Node identifier |\n| `MALTI_BATCH_SIZE` | `500` | Records per batch |\n| `MALTI_BATCH_INTERVAL` | `60.0` | Seconds between batch sends |\n| `MALTI_MAX_RETRIES` | `3` | Max retry attempts |\n| `MALTI_RETRY_DELAY` | `1.0` | Base retry delay (seconds) |\n| `MALTI_HTTP_TIMEOUT` | `30.0` | HTTP request timeout |\n| `MALTI_MAX_KEEPALIVE_CONNECTIONS` | `5` | Max keepalive connections |\n| `MALTI_MAX_CONNECTIONS` | `10` | Max total connections |\n| `MALTI_OVERFLOW_THRESHOLD_PERCENT` | `90.0` | Buffer overflow threshold |\n| `MALTI_CLEAN_MODE` | `true` | Ignore 401/404 responses |\n| `MALTI_USE_IP_AS_CONSUMER` | `false` | Use IP address as consumer fallback |\n| `MALTI_IP_ANONYMIZE` | `false` | Anonymize IP addresses (simple octet masking) |\n\n### Programmatic Configuration\n\n```python\nfrom malti_telemetry import configure_malti\n\nconfigure_malti(\n service_name=\"my-service\",\n api_key=\"your-api-key\",\n malti_url=\"https://api.malti.muzy.dev\",\n node=\"prod-web-01\",\n batch_size=1000,\n clean_mode=True,\n use_ip_as_consumer=True,\n ip_anonymize=True\n)\n```\n\n## Advanced Usage\n\n### Framework-Specific Features\n\n#### FastAPI Features\n\n**Route Pattern Extraction**: Automatic conversion of actual paths to route patterns:\n- `/users/123` \u2192 `/users/{user_id}`\n- `/api/v1/posts/456/comments` \u2192 `/api/v1/posts/{post_id}/comments`\n- Works with nested routes and mount points\n\n**Context Information**: Add context using FastAPI's request state:\n\n```python\nfrom fastapi import Request\n\n@app.route(\"/users/{user_id}\")\nasync def get_user(request):\n user_id = request.path_params[\"user_id\"]\n if user_id < 1000:\n request.state.context = \"legacy\"\n else:\n request.state.context = \"current\"\n return JSONResponse({\"user_id\": user_id, \"name\": \"John Doe\"})\n```\n\n#### Starlette Features\n\n**Automatic Lifespan Management**: Telemetry system starts/stops automatically:\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.middleware import Middleware\nfrom malti_telemetry.middleware import MaltiMiddleware\n\napp = Starlette()\napp.add_middleware(Middleware(MaltiMiddleware)) # Lifespan auto-injected!\n\n# No need for manual lifespan management!\n```\n\n**Route Pattern Extraction**: Automatically extracts route patterns from Starlette routing:\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.middleware import Middleware\nfrom starlette.routing import Route, Mount\nfrom malti_telemetry.middleware import MaltiMiddleware\n\napp = Starlette()\napp.add_middleware(Middleware(MaltiMiddleware))\n\nasync def user_handler(request):\n return JSONResponse({\"user_id\": request.path_params[\"user_id\"]})\n\nasync def post_handler(request):\n return JSONResponse({\"post_id\": request.path_params[\"post_id\"]})\n\n# Works with all Starlette routing patterns\napp.routes = [\n Route(\"/api/v1/users/{user_id}\", endpoint=user_handler),\n Mount(\"/api/v2\", routes=[\n Route(\"/posts/{post_id}\", endpoint=post_handler),\n ]),\n]\n\n# Automatically recorded as:\n# method=GET, endpoint=\"/api/v1/users/{user_id}\"\n# method=GET, endpoint=\"/api/v2/posts/{post_id}\"\n```\n\n### Consumer Identification\n\nMalti automatically extracts consumer information from headers:\n\n1. `x-consumer-id` header \n2. `x-user-id` header \n3. `consumer-id` header\n4. `user-id` header\n\n**IP Address as Consumer**: When no consumer headers are found, you can configure Malti to use IP addresses as a fallback:\n\n```python\n# Enable IP address extraction from X-Forwarded-For header\nconfigure_malti(\n use_ip_as_consumer=True,\n ip_anonymize=True # Optional: anonymize IPs for privacy\n)\n\n# Or via environment variables\nexport MALTI_USE_IP_AS_CONSUMER=true\nexport MALTI_IP_ANONYMIZE=true\n```\n\n**IP Anonymization**: When enabled, IP addresses are anonymized using simple octet masking:\n- IPv4: `192.168.1.100` \u2192 `192.168.1.xxx`\n- IPv6: `2001:db8:85a3:8d3:1319:8a2e:370:7348` \u2192 `2001:db8:85a3:8d3:xxxx:xxxx:xxxx:xxxx`\n\n**IP Extraction Priority**:\n1. X-Forwarded-For header (uses first/leftmost IP)\n2. Direct client IP (fallback)\n\n**Custom Consumer Extraction**: Set consumer information in your framework:\n\n```python\n# FastAPI\n@app.middleware(\"http\")\nasync def set_consumer(request: Request, call_next):\n request.state.malti_consumer = \"app\"\n return await call_next(request)\n\n# Starlette\n@app.middleware(\"http\")\nasync def set_consumer(request, call_next):\n # Add consumer to ASGI scope\n request.scope[\"state\"][\"malti_consumer\"] = \"api\"\n response = await call_next(request)\n return response\n```\n\n### Manual Telemetry Recording\n\n```python\nfrom malti_telemetry import get_telemetry_system\n\ntelemetry = get_telemetry_system()\n\n# Record a custom event\ntelemetry.record_request(\n method=\"GET\",\n endpoint=\"/api/custom\",\n status=200,\n response_time=150,\n consumer=\"custom-client\",\n context=\"manual-recording\"\n)\n```\n\n### Statistics and Monitoring\n\n```python\nfrom malti_telemetry import get_malti_stats\n\nstats = get_malti_stats()\nprint(stats)\n# {\n# 'total_added': 1250,\n# 'total_sent': 1200,\n# 'total_failed': 50,\n# 'current_size': 50,\n# 'max_size': 25000,\n# 'service_name': 'my-service',\n# 'running': True\n# }\n```\n\n## Supported Frameworks\n\nMalti Telemetry works with any Starlette-compatible framework:\n\n- **FastAPI**: Enhanced route pattern extraction and request.state integration\n- **Starlette**: Base middleware with full functionality and lifespan management\n- **Responder**: Works with generic Starlette middleware\n- **Any ASGI framework**: Generic middleware for custom implementations\n\n### Architecture\n\n#### Core Components\n\n1. **TelemetryCollector**: Collects HTTP request telemetry data\n2. **BatchSender**: Sends batched telemetry data to Malti server\n3. **TelemetrySystem**: Combines collector and sender with unified interface\n4. **TelemetryBuffer**: Thread-safe buffer for storing records\n\n### Worker Process Model\n\nEach FastAPI/Uvicorn worker process gets its own telemetry system instance:\n\n```\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 Worker Process \u2502 \u2502 Worker Process \u2502\n\u2502 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502 \u2502 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502\n\u2502 \u2502TelemetrySys \u2502 \u2502 \u2502 \u2502TelemetrySys \u2502 \u2502\n\u2502 \u2502 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502 \u2502 \u2502 \u2502 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502 \u2502\n\u2502 \u2502 \u2502Buffer \u2502 \u2502 \u2502 \u2502 \u2502 \u2502Buffer \u2502 \u2502 \u2502\n\u2502 \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502 \u2502 \u2502 \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502 \u2502\n\u2502 \u2502 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502 \u2502 \u2502 \u2502 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502 \u2502\n\u2502 \u2502 \u2502Sender \u2502 \u2502 \u2502 \u2502 \u2502 \u2502Sender \u2502 \u2502 \u2502\n\u2502 \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502 \u2502 \u2502 \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502 \u2502\n\u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502 \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n```\n\n## Development\n\n### Setup\n\n```bash\ngit clone https://github.com/muzy/malti-telemetry.git\ncd python/\npip install -e \".[dev]\"\n```\n\n### Testing\n\n```bash\npytest\n```\n\n### Code Quality\n\n```bash\nblack malti_telemetry/\nisort malti_telemetry/\nmypy malti_telemetry/\nflake8 malti_telemetry/\n```\n\n## License\n\nMIT License - see [LICENSE](LICENSE) file for details.\n",
"bugtrack_url": null,
"license": null,
"summary": "A Python library for collecting and sending telemetry data to Malti server",
"version": "1.0.4",
"project_urls": {
"Changelog": "https://github.com/muzy/malti-telemetry/releases",
"Homepage": "https://github.com/muzy/malti-telemetry/",
"Issues": "https://github.com/muzy/malti-telemetry/issues",
"Repository": "https://github.com/muzy/malti-telemetry/"
},
"split_keywords": [
"telemetry",
" monitoring",
" fastapi",
" analytics",
" api",
" self-hosted",
" starlette"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "ba935c43dbd8636310da15ade683b5d0a5d0e59f097d01720aa99844398de752",
"md5": "64a9e74a4bf73d7d3435e77aa5844532",
"sha256": "84bb140cd1ba2cb8982dfc26ef590901deb72ed03e6da5503ae6107c15538915"
},
"downloads": -1,
"filename": "malti_telemetry-1.0.4-py3-none-any.whl",
"has_sig": false,
"md5_digest": "64a9e74a4bf73d7d3435e77aa5844532",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.11",
"size": 15301,
"upload_time": "2025-10-19T09:51:44",
"upload_time_iso_8601": "2025-10-19T09:51:44.293679Z",
"url": "https://files.pythonhosted.org/packages/ba/93/5c43dbd8636310da15ade683b5d0a5d0e59f097d01720aa99844398de752/malti_telemetry-1.0.4-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "ca8981d70e54fc2657183f6bd5ec0e4b4436158d559373ec09c84b23d4ee6301",
"md5": "3097f868c9cb5bad1cf3ad46171d6735",
"sha256": "c0b09e7914c71e90e6aa1e7ee0315023db0ab9b5b499dd70efacb04a5cdfb0d7"
},
"downloads": -1,
"filename": "malti_telemetry-1.0.4.tar.gz",
"has_sig": false,
"md5_digest": "3097f868c9cb5bad1cf3ad46171d6735",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.11",
"size": 26034,
"upload_time": "2025-10-19T09:51:45",
"upload_time_iso_8601": "2025-10-19T09:51:45.451348Z",
"url": "https://files.pythonhosted.org/packages/ca/89/81d70e54fc2657183f6bd5ec0e4b4436158d559373ec09c84b23d4ee6301/malti_telemetry-1.0.4.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-10-19 09:51:45",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "muzy",
"github_project": "malti-telemetry",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "malti-telemetry"
}