rebrand: Posthog->Insights, package hanzo-insights

- Rename main class Posthog -> Insights (Posthog kept as alias)
- Rename PosthogContextMiddleware -> InsightsContextMiddleware (alias kept)
- Rename PostHogTracingProcessor -> InsightsTracingProcessor (alias kept)
- Add `insights/` re-export package so `from insights import Insights` works
- Update all imports from `posthog` to `hanzo_insights` across source and tests
- Update docstrings, comments, error messages, user agent string
- Update README, example.py, .env.example, Makefile, LLM.md
- Django middleware now supports INSIGHTS_MW_* settings (POSTHOG_MW_* still works)
- Django middleware accepts X-INSIGHTS-* headers (X-POSTHOG-* still works)
- Keep protocol-level values ($lib, ingestion URLs, sentinel strings) for server compat
- Keep posthog_* parameter names in AI wrappers for API compat
- All 681 tests pass
This commit is contained in:
Hanzo Dev
2026-03-13 19:58:09 -07:00
parent 7105552a05
commit 9fb72596af
46 changed files with 547 additions and 520 deletions
+6 -6
View File
@@ -1,11 +1,11 @@
# PostHog API Configuration
# Hanzo Insights API Configuration
# Copy this file to .env and update with your actual values
# Your project API key (found on the /setup page in PostHog)
POSTHOG_PROJECT_API_KEY=phc_your_project_api_key_here
# Your project API key (found on the setup page in Insights)
INSIGHTS_PROJECT_API_KEY=hi_your_project_api_key_here
# Your personal API key (for local evaluation and other advanced features)
POSTHOG_PERSONAL_API_KEY=phx_your_personal_api_key_here
INSIGHTS_PERSONAL_API_KEY=phx_your_personal_api_key_here
# PostHog host URL (remove this line if using posthog.com)
POSTHOG_HOST=http://localhost:8000
# Insights host URL (remove this line if using insights.hanzo.ai)
INSIGHTS_HOST=http://localhost:8000
+7 -7
View File
@@ -1,6 +1,6 @@
# Before Send Hook
The `before_send` parameter allows you to modify or filter events before they are sent to PostHog. This is useful for:
The `before_send` parameter allows you to modify or filter events before they are sent to Insights. This is useful for:
- **Privacy**: Removing or masking sensitive data (PII)
- **Filtering**: Dropping unwanted events (test events, internal users, etc.)
@@ -10,12 +10,12 @@ The `before_send` parameter allows you to modify or filter events before they ar
## Basic Usage
```python
import posthog
import hanzo_insights
from typing import Optional, Dict, Any
def my_before_send(event: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""
Process event before sending to PostHog.
Process event before sending to Insights.
Args:
event: The event dictionary containing 'event', 'distinct_id', 'properties', etc.
@@ -27,7 +27,7 @@ def my_before_send(event: Dict[str, Any]) -> Optional[Dict[str, Any]]:
return event
# Initialize client with before_send hook
client = posthog.Client(
client = hanzo_insights.Client(
api_key="your-project-api-key",
before_send=my_before_send
)
@@ -166,7 +166,7 @@ def should_drop_event(event: dict[str, Any]) -> bool:
## Error Handling
If your `before_send` function raises an exception, PostHog will:
If your `before_send` function raises an exception, Insights will:
1. Log the error
2. Continue with the original, unmodified event
@@ -184,7 +184,7 @@ def risky_before_send(event: dict[str, Any]) -> Optional[dict[str, Any]]:
## Complete Example
```python
import posthog
import hanzo_insights
from typing import Optional, Any
import re
@@ -227,7 +227,7 @@ def production_before_send(event: dict[str, Any]) -> Optional[dict[str, Any]]:
return event # Return original event on error
# Usage
client = posthog.Client(
client = hanzo_insights.Client(
api_key="your-api-key",
before_send=production_before_send
)
+22 -19
View File
@@ -1,10 +1,11 @@
# LLM.md - Hanzo Posthog Python
# LLM.md - Hanzo Insights Python SDK
## Overview
Integrate Hanzo Insights into any python application.
Integrate Hanzo Insights into any Python application. Package name: `hanzo-insights` on PyPI.
## Tech Stack
- **Language**: Python
- **Language**: Python 3.10+
- **Package**: `hanzo_insights` (import name), `hanzo-insights` (pip name)
## Build & Run
```bash
@@ -15,24 +16,26 @@ uv run pytest
## Structure
```
posthog-python/
BEFORE_SEND.md
CHANGELOG.md
CODEOWNERS
LICENSE
MANIFEST.in
Makefile
README.md
README_ANALYTICS.md
bin/
e2e_test.sh
example.py
hanzo_insights/ # Main package
__init__.py # Module-level API, Insights class (alias: Posthog)
client.py # Client class
ai/ # AI provider integrations (OpenAI, Anthropic, Gemini, LangChain)
integrations/ # Framework integrations (Django middleware)
test/ # Tests
examples/
hanzo_insights/
integration_tests/
mypy-baseline.txt
pyproject.toml # Package config (name: hanzo-insights)
setup.py # Legacy setup
```
## Key Files
- `README.md` -- Project documentation
- `pyproject.toml` -- Python project config
- `Makefile` -- Build automation
- `pyproject.toml` -- Package config, dependencies, test config
- `hanzo_insights/__init__.py` -- Public API surface
- `hanzo_insights/client.py` -- Client implementation
## Rebrand Notes
- Main class: `Insights` (backward compat alias: `Posthog = Insights`)
- Django middleware: `InsightsContextMiddleware` (alias: `PosthogContextMiddleware`)
- OpenAI Agents: `InsightsTracingProcessor` (alias: `PostHogTracingProcessor`)
- Internal protocol values (`$lib`, `posthog.com` ingestion URLs) kept for server compat
- `posthog_*` parameter names in AI wrappers kept for API compat
+22 -21
View File
@@ -9,37 +9,38 @@ build_release:
rm -rf dist/*
python setup.py sdist bdist_wheel
# Builds the `posthoganalytics` PyPI package, which is a mirror of `posthog`
# published under a different name for internal use by posthog/posthog.
# Builds the `posthoganalytics` PyPI package, which is a mirror of `hanzo_insights`
# published under a different name for backward compatibility with the upstream
# posthog/posthog project.
#
# The process works in three phases:
# 1. posthog -> posthoganalytics: Copy the source, rewrite all imports,
# remove the original posthog/ dir, and build the dist.
# 2. posthoganalytics -> posthog: Reverse the import rewrites, copy
# everything back into posthog/, and clean up.
# 1. hanzo_insights -> posthoganalytics: Copy the source, rewrite all imports,
# remove the original hanzo_insights/ dir, and build the dist.
# 2. posthoganalytics -> hanzo_insights: Reverse the import rewrites, copy
# everything back into hanzo_insights/, and clean up.
# 3. Restore pyproject.toml from backup (setup_analytics.py modifies it).
#
# This ensures the working tree is left in the same state it started in.
#
# NOTE: This target clears dist/ before building. In the release workflow,
# `build_release` (posthog) must be published BEFORE running this target,
# otherwise the posthog dist artifacts will be lost.
# `build_release` (hanzo_insights) must be published BEFORE running this target,
# otherwise the hanzo_insights dist artifacts will be lost.
build_release_analytics:
rm -rf dist
rm -rf build
rm -rf posthoganalytics
mkdir posthoganalytics
cp -r posthog/* posthoganalytics/
find ./posthoganalytics -type f -name "*.py" -exec sed -i.bak -e 's/from posthog /from posthoganalytics /g' {} \;
find ./posthoganalytics -type f -name "*.py" -exec sed -i.bak -e 's/from posthog\./from posthoganalytics\./g' {} \;
cp -r hanzo_insights/* posthoganalytics/
find ./posthoganalytics -type f -name "*.py" -exec sed -i.bak -e 's/from hanzo_insights /from posthoganalytics /g' {} \;
find ./posthoganalytics -type f -name "*.py" -exec sed -i.bak -e 's/from hanzo_insights\./from posthoganalytics\./g' {} \;
find ./posthoganalytics -name "*.bak" -delete
rm -rf posthog
rm -rf hanzo_insights
python setup_analytics.py sdist bdist_wheel
mkdir posthog
find ./posthoganalytics -type f -name "*.py" -exec sed -i.bak -e 's/from posthoganalytics /from posthog /g' {} \;
find ./posthoganalytics -type f -name "*.py" -exec sed -i.bak -e 's/from posthoganalytics\./from posthog\./g' {} \;
mkdir hanzo_insights
find ./posthoganalytics -type f -name "*.py" -exec sed -i.bak -e 's/from posthoganalytics /from hanzo_insights /g' {} \;
find ./posthoganalytics -type f -name "*.py" -exec sed -i.bak -e 's/from posthoganalytics\./from hanzo_insights\./g' {} \;
find ./posthoganalytics -name "*.bak" -delete
cp -r posthoganalytics/* posthog/
cp -r posthoganalytics/* hanzo_insights/
rm -rf posthoganalytics
rm -f pyproject.toml
cp pyproject.toml.backup pyproject.toml
@@ -54,14 +55,14 @@ prep_local:
cp -r . ../posthog-python-local/
cd ../posthog-python-local && rm -rf dist build posthoganalytics .git
cd ../posthog-python-local && mkdir posthoganalytics
cd ../posthog-python-local && cp -r posthog/* posthoganalytics/
cd ../posthog-python-local && find ./posthoganalytics -type f -name "*.py" -exec sed -i.bak -e 's/from posthog /from posthoganalytics /g' {} \;
cd ../posthog-python-local && find ./posthoganalytics -type f -name "*.py" -exec sed -i.bak -e 's/from posthog\./from posthoganalytics\./g' {} \;
cd ../posthog-python-local && cp -r hanzo_insights/* posthoganalytics/
cd ../posthog-python-local && find ./posthoganalytics -type f -name "*.py" -exec sed -i.bak -e 's/from hanzo_insights /from posthoganalytics /g' {} \;
cd ../posthog-python-local && find ./posthoganalytics -type f -name "*.py" -exec sed -i.bak -e 's/from hanzo_insights\./from posthoganalytics\./g' {} \;
cd ../posthog-python-local && find ./posthoganalytics -name "*.bak" -delete
cd ../posthog-python-local && rm -rf posthog
cd ../posthog-python-local && rm -rf hanzo_insights
cd ../posthog-python-local && sed -i.bak 's/from version import VERSION/from posthoganalytics.version import VERSION/' setup_analytics.py
cd ../posthog-python-local && rm setup_analytics.py.bak
cd ../posthog-python-local && sed -i.bak 's/"posthog"/"posthoganalytics"/' setup.py
cd ../posthog-python-local && sed -i.bak 's/"hanzo_insights"/"posthoganalytics"/' setup.py
cd ../posthog-python-local && rm setup.py.bak
cd ../posthog-python-local && python -c "import setup_analytics" 2>/dev/null || true
@echo "Local copy created at ../posthog-python-local"
+53 -54
View File
@@ -1,41 +1,51 @@
# PostHog Python
# Hanzo Insights Python SDK
<p align="center">
<img alt="posthoglogo" src="https://user-images.githubusercontent.com/65415371/205059737-c8a4f836-4889-4654-902e-f302b187b6a0.png">
</p>
<p align="center">
<a href="https://pypi.org/project/posthog/"><img alt="pypi installs" src="https://img.shields.io/pypi/v/posthog"/></a>
<img alt="GitHub contributors" src="https://img.shields.io/github/contributors/posthog/posthog-python">
<img alt="GitHub commit activity" src="https://img.shields.io/github/commit-activity/m/posthog/posthog-python"/>
<img alt="GitHub closed issues" src="https://img.shields.io/github/issues-closed/posthog/posthog-python"/>
</p>
Integrate [Hanzo Insights](https://insights.hanzo.ai) into any Python application.
Please see the [Python integration docs](https://posthog.com/docs/integrations/python-integration) for details.
## Installation
```bash
pip install hanzo-insights
```
## Quick Start
```python
from hanzo_insights import Insights
client = Insights('<your_project_api_key>', host='https://insights.hanzo.ai')
# Capture an event
client.capture('user_123', 'purchase', properties={'product': 'widget'})
# Feature flags
if client.feature_enabled('new-checkout', 'user_123'):
show_new_checkout()
```
## Module-level usage
```python
import hanzo_insights
hanzo_insights.api_key = '<your_project_api_key>'
hanzo_insights.host = 'https://insights.hanzo.ai'
hanzo_insights.capture('movie_played', distinct_id='user_123', properties={'movie_id': '42'})
hanzo_insights.shutdown()
```
## Python Version Support
| SDK Version | Python Versions Supported | Notes |
| ------------- | ---------------------------- | -------------------------- |
| 7.3.1+ | 3.10, 3.11, 3.12, 3.13, 3.14 | Added Python 3.14 support |
| 7.0.0 - 7.0.1 | 3.10, 3.11, 3.12, 3.13 | Dropped Python 3.9 support |
| 4.0.1 - 6.x | 3.9, 3.10, 3.11, 3.12, 3.13 | Python 3.9+ required |
| SDK Version | Python Versions Supported |
| -------------- | ----------------------------- |
| 7.3.1+ | 3.10, 3.11, 3.12, 3.13, 3.14 |
| 7.0.0 - 7.0.1 | 3.10, 3.11, 3.12, 3.13 |
| 4.0.1 - 6.x | 3.9, 3.10, 3.11, 3.12, 3.13 |
## Development
### Testing Locally
We recommend using [uv](https://docs.astral.sh/uv/). It's super fast.
1. Run `uv venv env` (creates virtual environment called "env")
- or `python3 -m venv env`
2. Run `source env/bin/activate` (activates the virtual environment)
3. Run `uv sync --extra dev --extra test` (installs the package in develop mode, along with test dependencies)
- or `pip install -e ".[dev,test]"`
4. you have to run `pre-commit install` to have auto linting pre commit
5. Run `make test`
6. To run a specific test do `pytest -k test_no_api_key`
## PostHog recommends `uv` so...
We use [uv](https://docs.astral.sh/uv/).
```bash
uv python install 3.12
@@ -47,34 +57,23 @@ pre-commit install
make test
```
### Running Locally
### Running Tests
Assuming you have a [local version of PostHog](https://posthog.com/docs/developing-locally) running, you can run `python3 example.py` to see the library in action.
### Testing changes locally with the PostHog app
You can run `make prep_local`, and it'll create a new folder alongside the SDK repo one called `posthog-python-local`, which you can then import into the posthog project by changing pyproject.toml to look like this:
```toml
dependencies = [
...
"posthoganalytics" #NOTE: no version number
...
]
...
[tools.uv.sources]
posthoganalytics = { path = "../posthog-python-local" }
```bash
make test
# or run a specific test:
pytest -k test_no_api_key
```
This'll let you build and test SDK changes fully locally, incorporating them into your local posthog app stack. It mainly takes care of the `posthog -> posthoganalytics` module renaming. You'll need to re-run `make prep_local` each time you make a change, and re-run `uv sync --active` in the posthog app project.
## Backward Compatibility
## Releasing
For users migrating from `posthog` or `posthoganalytics`, the `Posthog` class name is
available as an alias for `Insights`:
This repository uses [Sampo](https://github.com/bruits/sampo) for versioning, changelogs, and publishing to crates.io.
```python
from hanzo_insights import Posthog # works, same as Insights
```
1. When making changes, include a changeset: `sampo add`
2. Create a PR with your changes and the changeset file
3. Add the `release` label and merge to `main`
4. Approve the release in Slack when prompted — this triggers version bump, crates.io publish, git tag, and GitHub Release
## License
You can also trigger a release manually via the workflow's `workflow_dispatch` trigger (still requires pending changesets).
MIT
+14 -14
View File
@@ -1,18 +1,18 @@
# PostHog Python library example
# Hanzo Insights Python library example
#
# This script demonstrates various PostHog Python SDK capabilities including:
# This script demonstrates various Hanzo Insights Python SDK capabilities including:
# - Basic event capture and user identification
# - Feature flag local evaluation
# - Feature flag payloads
# - Context management and tagging
#
# Setup:
# 1. Copy .env.example to .env and fill in your PostHog credentials
# 1. Copy .env.example to .env and fill in your Insights credentials
# 2. Run this script and choose from the interactive menu
import os
import posthog
import hanzo_insights
def load_env_file():
@@ -31,18 +31,18 @@ def load_env_file():
load_env_file()
# Get configuration
project_key = os.getenv("POSTHOG_PROJECT_API_KEY", "")
personal_api_key = os.getenv("POSTHOG_PERSONAL_API_KEY", "")
host = os.getenv("POSTHOG_HOST", "http://localhost:8000")
project_key = os.getenv("INSIGHTS_PROJECT_API_KEY", "")
personal_api_key = os.getenv("INSIGHTS_PERSONAL_API_KEY", "")
host = os.getenv("INSIGHTS_HOST", "http://localhost:8000")
# Check if project key is provided (required)
if not project_key:
print("❌ Missing PostHog project API key!")
print(" Please set POSTHOG_PROJECT_API_KEY environment variable")
print("❌ Missing Insights project API key!")
print(" Please set INSIGHTS_PROJECT_API_KEY environment variable")
print(" or copy .env.example to .env and fill in your values")
exit(1)
# Configure PostHog with credentials
# Configure Insights with credentials
hanzo_insights.debug = False
hanzo_insights.api_key = project_key
hanzo_insights.project_api_key = project_key
@@ -54,7 +54,7 @@ local_eval_available = bool(personal_api_key)
if personal_api_key:
hanzo_insights.personal_api_key = personal_api_key
print("🔑 PostHog Configuration:")
print("🔑 Insights Configuration:")
print(f" Project API Key: {project_key[:9]}...")
if local_eval_available:
print(" Personal API Key: [SET]")
@@ -63,7 +63,7 @@ else:
print(f" Host: {host}\n")
# Display menu and get user choice
print("🚀 PostHog Python SDK Demo - Choose an example to run:\n")
print("🚀 Hanzo Insights Python SDK Demo - Choose an example to run:\n")
print("1. Identify and capture examples")
local_eval_note = "" if local_eval_available else " [requires personal API key]"
print(f"2. Feature flag local evaluation examples{local_eval_note}")
@@ -137,7 +137,7 @@ elif choice == "2":
if not local_eval_available:
print("\n❌ This example requires a personal API key for local evaluation.")
print(
" Set POSTHOG_PERSONAL_API_KEY environment variable to run this example."
" Set INSIGHTS_PERSONAL_API_KEY environment variable to run this example."
)
hanzo_insights.shutdown()
exit(1)
@@ -212,7 +212,7 @@ elif choice == "4":
if not local_eval_available:
print("\n❌ This example requires a personal API key for local evaluation.")
print(
" Set POSTHOG_PERSONAL_API_KEY environment variable to run this example."
" Set INSIGHTS_PERSONAL_API_KEY environment variable to run this example."
)
hanzo_insights.shutdown()
exit(1)
+7 -7
View File
@@ -1,17 +1,17 @@
"""
Redis-based distributed cache for PostHog feature flag definitions.
Redis-based distributed cache for Insights feature flag definitions.
This example demonstrates how to implement a FlagDefinitionCacheProvider
using Redis for multi-instance deployments (leader election pattern).
Usage:
import redis
from posthog import Posthog
from hanzo_insights import Posthog
redis_client = redis.Redis(host='localhost', port=6379, decode_responses=True)
cache = RedisFlagCache(redis_client, service_key="my-service")
posthog = Posthog(
client = Insights(
"<project_api_key>",
personal_api_key="<personal_api_key>",
flag_definition_cache_provider=cache,
@@ -24,17 +24,17 @@ Requirements:
import json
import uuid
from posthog import FlagDefinitionCacheData, FlagDefinitionCacheProvider
from hanzo_insights import FlagDefinitionCacheData, FlagDefinitionCacheProvider
from redis import Redis
from typing import Optional
class RedisFlagCache(FlagDefinitionCacheProvider):
"""
A distributed cache for PostHog feature flag definitions using Redis.
A distributed cache for Insights feature flag definitions using Redis.
In a multi-instance deployment (e.g., multiple serverless functions or containers),
we want only ONE instance to poll PostHog for flag updates, while all instances
we want only ONE instance to poll Insights for flag updates, while all instances
share the cached results. This prevents N instances from making N redundant API calls.
The implementation uses leader election:
@@ -113,7 +113,7 @@ class RedisFlagCache(FlagDefinitionCacheProvider):
def should_fetch_flag_definitions(self) -> bool:
"""
Determines if this instance should fetch flag definitions from PostHog.
Determines if this instance should fetch flag definitions from Insights.
Atomically either:
- Acquires the lock if no one holds it, OR
+4 -4
View File
@@ -1,14 +1,14 @@
#!/usr/bin/env python3
"""
Simple test script for PostHog remote config endpoint.
Simple test script for Insights remote config endpoint.
"""
import posthog
import hanzo_insights
# Initialize PostHog client
# Initialize Insights client
hanzo_insights.api_key = "phc_..."
hanzo_insights.personal_api_key = "phs_..." # or "phx_..."
hanzo_insights.host = "http://localhost:8000" # or "https://us.hanzo_insights.com"
hanzo_insights.host = "http://localhost:8000" # or "https://us.posthog.com"
hanzo_insights.debug = True
+33 -27
View File
@@ -76,11 +76,11 @@ def new_context(fresh=False, capture_exceptions=True, client=None):
Args:
fresh: Whether to start with a fresh context (default: False)
capture_exceptions: Whether to capture exceptions raised within the context (default: True)
client: Optional Posthog client instance to use for this context (default: None)
client: Optional Insights client instance to use for this context (default: None)
Examples:
```python
from posthog import new_context, tag, capture
from hanzo_insights import new_context, tag, capture
with new_context():
tag("request_id", "123")
capture("event_name", properties={"property": "value"})
@@ -100,11 +100,11 @@ def scoped(fresh=False, capture_exceptions=True):
Args:
fresh: Whether to start with a fresh context (default: False)
capture_exceptions: Whether to capture and track exceptions with posthog error tracking (default: True)
capture_exceptions: Whether to capture and track exceptions with Insights error tracking (default: True)
Examples:
```python
from posthog import scoped, tag, capture
from hanzo_insights import scoped, tag, capture
@scoped()
def process_payment(payment_id):
tag("payment_id", payment_id)
@@ -126,7 +126,7 @@ def set_context_session(session_id: str):
Examples:
```python
from posthog import set_context_session
from hanzo_insights import set_context_session
set_context_session("session_123")
```
@@ -146,7 +146,7 @@ def set_context_device_id(device_id: str):
Examples:
```python
from posthog import set_context_device_id
from hanzo_insights import set_context_device_id
set_context_device_id("device_123")
```
@@ -165,7 +165,7 @@ def identify_context(distinct_id: str):
Examples:
```python
from posthog import identify_context
from hanzo_insights import identify_context
identify_context("user_123")
```
@@ -206,7 +206,7 @@ def tag(name: str, value: Any):
Examples:
```python
from posthog import tag
from hanzo_insights import tag
tag("user_id", "123")
```
@@ -280,12 +280,12 @@ def capture(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str]:
disable_geoip: Whether to disable GeoIP lookup
Details:
Capture allows you to capture anything a user does within your system, which you can later use in PostHog to find patterns in usage, work out which features to improve or where people are giving up. A capture call requires an event name to specify the event. We recommend using [verb] [noun], like `movie played` or `movie updated` to easily identify what your events mean later on. Capture takes a number of optional arguments, which are defined by the `OptionalCaptureArgs` type.
Capture allows you to capture anything a user does within your system, which you can later use in Insights to find patterns in usage, work out which features to improve or where people are giving up. A capture call requires an event name to specify the event. We recommend using [verb] [noun], like `movie played` or `movie updated` to easily identify what your events mean later on. Capture takes a number of optional arguments, which are defined by the `OptionalCaptureArgs` type.
Examples:
```python
# Context and capture usage
from posthog import new_context, identify_context, tag_context, capture
from hanzo_insights import new_context, identify_context, tag_context, capture
# Enter a new context (e.g. a request/response cycle, an instance of a background job, etc)
with new_context():
# Associate this context with some user, by distinct_id
@@ -312,7 +312,7 @@ def capture(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str]:
```
```python
# Set event properties
from posthog import capture
from hanzo_insights import capture
capture(
"user_signed_up",
distinct_id="distinct_id_of_the_user",
@@ -339,7 +339,7 @@ def set(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str]:
Examples:
```python
# Set person properties
from posthog import capture
from hanzo_insights import capture
capture(
'distinct_id',
event='event_name',
@@ -366,7 +366,7 @@ def set_once(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str]:
Examples:
```python
# Set property once
from posthog import capture
from hanzo_insights import capture
capture(
'distinct_id',
event='event_name',
@@ -406,7 +406,7 @@ def group_identify(
Examples:
```python
# Group identify
from posthog import group_identify
from hanzo_insights import group_identify
group_identify('company', 'company_id_in_your_db', {
'name': 'Awesome Inc.',
'employees': 11
@@ -451,7 +451,7 @@ def alias(
Examples:
```python
# Alias user
from posthog import alias
from hanzo_insights import alias
alias(previous_id='distinct_id', distinct_id='alias_id')
```
Category:
@@ -484,7 +484,7 @@ def capture_exception(
Examples:
```python
# Capture exception
from posthog import capture_exception
from hanzo_insights import capture_exception
try:
risky_operation()
except Exception as e:
@@ -528,7 +528,7 @@ def feature_enabled(
Examples:
```python
# Boolean feature flag
from posthog import feature_enabled, get_feature_flag_payload
from hanzo_insights import feature_enabled, get_feature_flag_payload
is_my_flag_enabled = feature_enabled('flag-key', 'distinct_id_of_your_user')
if is_my_flag_enabled:
matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')
@@ -575,12 +575,12 @@ def get_feature_flag(
disable_geoip: Whether to disable GeoIP lookup
Details:
`groups` are a mapping from group type to group key. So, if you have a group type of "organization" and a group key of "5", you would pass groups={"organization": "5"}. `group_properties` take the format: { group_type_name: { group_properties } }. So, for example, if you have the group type "organization" and the group key "5", with the properties name, and employee count, you'll send these as: group_properties={"organization": {"name": "PostHog", "employees": 11}}.
`groups` are a mapping from group type to group key. So, if you have a group type of "organization" and a group key of "5", you would pass groups={"organization": "5"}. `group_properties` take the format: { group_type_name: { group_properties } }. So, for example, if you have the group type "organization" and the group key "5", with the properties name, and employee count, you'll send these as: group_properties={"organization": {"name": "Hanzo", "employees": 11}}.
Examples:
```python
# Multivariate feature flag
from posthog import get_feature_flag, get_feature_flag_payload
from hanzo_insights import get_feature_flag, get_feature_flag_payload
enabled_variant = get_feature_flag('flag-key', 'distinct_id_of_your_user')
if enabled_variant == 'variant-key':
matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')
@@ -628,7 +628,7 @@ def get_all_flags(
Examples:
```python
# All flags for user
from posthog import get_all_flags
from hanzo_insights import get_all_flags
get_all_flags('distinct_id_of_your_user')
```
Category:
@@ -768,7 +768,7 @@ def feature_flag_definitions():
Examples:
```python
from posthog import feature_flag_definitions
from hanzo_insights import feature_flag_definitions
definitions = feature_flag_definitions()
```
@@ -780,11 +780,11 @@ def feature_flag_definitions():
def load_feature_flags():
"""
Load feature flag definitions from PostHog.
Load feature flag definitions from the server.
Examples:
```python
from posthog import load_feature_flags
from hanzo_insights import load_feature_flags
load_feature_flags()
```
@@ -800,7 +800,7 @@ def flush():
Examples:
```python
from posthog import flush
from hanzo_insights import flush
flush()
```
@@ -816,7 +816,7 @@ def join():
Examples:
```python
from posthog import join
from hanzo_insights import join
join()
```
@@ -832,7 +832,7 @@ def shutdown():
Examples:
```python
from posthog import shutdown
from hanzo_insights import shutdown
shutdown()
```
@@ -888,5 +888,11 @@ def _proxy(method, *args, **kwargs):
return fn(*args, **kwargs)
class Posthog(Client):
class Insights(Client):
"""Hanzo Insights client for product analytics."""
pass
# Backward compatibility alias
Posthog = Insights
+7 -7
View File
@@ -23,21 +23,21 @@ from hanzo_insights.ai.anthropic.anthropic_converter import (
finalize_anthropic_tool_input,
)
from hanzo_insights.ai.sanitization import sanitize_anthropic
from hanzo_insights.client import Client as PostHogClient
from posthog import setup
from hanzo_insights.client import Client as InsightsClient
from hanzo_insights import setup
class Anthropic(anthropic.Anthropic):
"""
A wrapper around the Anthropic SDK that automatically sends LLM usage events to PostHog.
A wrapper around the Anthropic SDK that automatically sends LLM usage events to Insights.
"""
_ph_client: PostHogClient
_ph_client: InsightsClient
def __init__(self, posthog_client: Optional[PostHogClient] = None, **kwargs):
def __init__(self, posthog_client: Optional[InsightsClient] = None, **kwargs):
"""
Args:
posthog_client: PostHog client for tracking usage
posthog_client: Insights client for tracking usage
**kwargs: Additional arguments passed to the Anthropic client
"""
super().__init__(**kwargs)
@@ -58,7 +58,7 @@ class WrappedMessages(Messages):
**kwargs: Any,
):
"""
Create a message using Anthropic's API while tracking usage in PostHog.
Create a message using Anthropic's API while tracking usage in Insights.
Args:
posthog_distinct_id: Optional ID to associate with the usage event
@@ -10,7 +10,7 @@ import time
import uuid
from typing import Any, Dict, List, Optional
from posthog import setup
from hanzo_insights import setup
from hanzo_insights.ai.types import StreamingContentBlock, TokenUsage, ToolInProgress
from hanzo_insights.ai.utils import (
call_llm_and_track_usage_async,
@@ -24,20 +24,20 @@ from hanzo_insights.ai.anthropic.anthropic_converter import (
finalize_anthropic_tool_input,
)
from hanzo_insights.ai.sanitization import sanitize_anthropic
from hanzo_insights.client import Client as PostHogClient
from hanzo_insights.client import Client as InsightsClient
class AsyncAnthropic(anthropic.AsyncAnthropic):
"""
An async wrapper around the Anthropic SDK that automatically sends LLM usage events to PostHog.
An async wrapper around the Anthropic SDK that automatically sends LLM usage events to Insights.
"""
_ph_client: PostHogClient
_ph_client: InsightsClient
def __init__(self, posthog_client: Optional[PostHogClient] = None, **kwargs):
def __init__(self, posthog_client: Optional[InsightsClient] = None, **kwargs):
"""
Args:
posthog_client: PostHog client for tracking usage
posthog_client: Insights client for tracking usage
**kwargs: Additional arguments passed to the Anthropic client
"""
super().__init__(**kwargs)
@@ -58,7 +58,7 @@ class AsyncWrappedMessages(AsyncMessages):
**kwargs: Any,
):
"""
Create a message using Anthropic's API while tracking usage in PostHog.
Create a message using Anthropic's API while tracking usage in Insights.
Args:
posthog_distinct_id: Optional ID to associate with the usage event
@@ -2,7 +2,7 @@
Anthropic-specific conversion utilities.
This module handles the conversion of Anthropic API responses and inputs
into standardized formats for PostHog tracking.
into standardized formats for Insights tracking.
"""
import json
@@ -425,7 +425,7 @@ def format_anthropic_streaming_input(kwargs: Dict[str, Any]) -> Any:
kwargs: Keyword arguments passed to Anthropic API
Returns:
Formatted input ready for PostHog tracking
Formatted input ready for Insights tracking
"""
from hanzo_insights.ai.utils import merge_system_prompt
@@ -445,7 +445,7 @@ def format_anthropic_streaming_output_complete(
accumulated_content: Raw accumulated text content as fallback
Returns:
Formatted messages ready for PostHog tracking
Formatted messages ready for Insights tracking
"""
formatted_content = format_anthropic_streaming_content(content_blocks)
@@ -9,18 +9,18 @@ from typing import Optional
from hanzo_insights.ai.anthropic.anthropic import WrappedMessages
from hanzo_insights.ai.anthropic.anthropic_async import AsyncWrappedMessages
from hanzo_insights.client import Client as PostHogClient
from posthog import setup
from hanzo_insights.client import Client as InsightsClient
from hanzo_insights import setup
class AnthropicBedrock(anthropic.AnthropicBedrock):
"""
A wrapper around the Anthropic Bedrock SDK that automatically sends LLM usage events to PostHog.
A wrapper around the Anthropic Bedrock SDK that automatically sends LLM usage events to Insights.
"""
_ph_client: PostHogClient
_ph_client: InsightsClient
def __init__(self, posthog_client: Optional[PostHogClient] = None, **kwargs):
def __init__(self, posthog_client: Optional[InsightsClient] = None, **kwargs):
super().__init__(**kwargs)
self._ph_client = posthog_client or setup()
self.messages = WrappedMessages(self)
@@ -28,12 +28,12 @@ class AnthropicBedrock(anthropic.AnthropicBedrock):
class AsyncAnthropicBedrock(anthropic.AsyncAnthropicBedrock):
"""
A wrapper around the Anthropic Bedrock SDK that automatically sends LLM usage events to PostHog.
A wrapper around the Anthropic Bedrock SDK that automatically sends LLM usage events to Insights.
"""
_ph_client: PostHogClient
_ph_client: InsightsClient
def __init__(self, posthog_client: Optional[PostHogClient] = None, **kwargs):
def __init__(self, posthog_client: Optional[InsightsClient] = None, **kwargs):
super().__init__(**kwargs)
self._ph_client = posthog_client or setup()
self.messages = AsyncWrappedMessages(self)
@@ -41,12 +41,12 @@ class AsyncAnthropicBedrock(anthropic.AsyncAnthropicBedrock):
class AnthropicVertex(anthropic.AnthropicVertex):
"""
A wrapper around the Anthropic Vertex SDK that automatically sends LLM usage events to PostHog.
A wrapper around the Anthropic Vertex SDK that automatically sends LLM usage events to Insights.
"""
_ph_client: PostHogClient
_ph_client: InsightsClient
def __init__(self, posthog_client: Optional[PostHogClient] = None, **kwargs):
def __init__(self, posthog_client: Optional[InsightsClient] = None, **kwargs):
super().__init__(**kwargs)
self._ph_client = posthog_client or setup()
self.messages = WrappedMessages(self)
@@ -54,12 +54,12 @@ class AnthropicVertex(anthropic.AnthropicVertex):
class AsyncAnthropicVertex(anthropic.AsyncAnthropicVertex):
"""
A wrapper around the Anthropic Vertex SDK that automatically sends LLM usage events to PostHog.
A wrapper around the Anthropic Vertex SDK that automatically sends LLM usage events to Insights.
"""
_ph_client: PostHogClient
_ph_client: InsightsClient
def __init__(self, posthog_client: Optional[PostHogClient] = None, **kwargs):
def __init__(self, posthog_client: Optional[InsightsClient] = None, **kwargs):
super().__init__(**kwargs)
self._ph_client = posthog_client or setup()
self.messages = AsyncWrappedMessages(self)
+19 -19
View File
@@ -13,7 +13,7 @@ except ImportError:
"Please install the Google Gemini SDK to use this feature: 'pip install google-genai'"
)
from posthog import setup
from hanzo_insights import setup
from hanzo_insights.ai.utils import (
call_llm_and_track_usage,
capture_streaming_event,
@@ -25,12 +25,12 @@ from hanzo_insights.ai.gemini.gemini_converter import (
format_gemini_streaming_output,
)
from hanzo_insights.ai.sanitization import sanitize_gemini
from hanzo_insights.client import Client as PostHogClient
from hanzo_insights.client import Client as InsightsClient
class Client:
"""
A drop-in replacement for genai.Client that automatically sends LLM usage events to PostHog.
A drop-in replacement for genai.Client that automatically sends LLM usage events to Insights.
Usage:
client = Client(
@@ -46,7 +46,7 @@ class Client:
)
"""
_ph_client: PostHogClient
_ph_client: InsightsClient
def __init__(
self,
@@ -57,7 +57,7 @@ class Client:
location: Optional[str] = None,
debug_config: Optional[Any] = None,
http_options: Optional[Any] = None,
posthog_client: Optional[PostHogClient] = None,
posthog_client: Optional[InsightsClient] = None,
posthog_distinct_id: Optional[str] = None,
posthog_properties: Optional[Dict[str, Any]] = None,
posthog_privacy_mode: bool = False,
@@ -73,7 +73,7 @@ class Client:
location: GCP location for Vertex AI
debug_config: Debug configuration for the client
http_options: HTTP options for the client
posthog_client: PostHog client for tracking usage
posthog_client: Insights client for tracking usage
posthog_distinct_id: Default distinct ID for all calls (can be overridden per call)
posthog_properties: Default properties for all calls (can be overridden per call)
posthog_privacy_mode: Default privacy mode for all calls (can be overridden per call)
@@ -84,7 +84,7 @@ class Client:
self._ph_client = posthog_client or setup()
if self._ph_client is None:
raise ValueError("posthog_client is required for PostHog tracking")
raise ValueError("posthog_client is required for Insights tracking")
self.models = Models(
api_key=api_key,
@@ -105,10 +105,10 @@ class Client:
class Models:
"""
Models interface that mimics genai.Client().models with PostHog tracking.
Models interface that mimics genai.Client().models with Insights tracking.
"""
_ph_client: PostHogClient # Not None after __init__ validation
_ph_client: InsightsClient # Not None after __init__ validation
def __init__(
self,
@@ -119,7 +119,7 @@ class Models:
location: Optional[str] = None,
debug_config: Optional[Any] = None,
http_options: Optional[Any] = None,
posthog_client: Optional[PostHogClient] = None,
posthog_client: Optional[InsightsClient] = None,
posthog_distinct_id: Optional[str] = None,
posthog_properties: Optional[Dict[str, Any]] = None,
posthog_privacy_mode: bool = False,
@@ -135,7 +135,7 @@ class Models:
location: GCP location for Vertex AI
debug_config: Debug configuration for the client
http_options: HTTP options for the client
posthog_client: PostHog client for tracking usage
posthog_client: Insights client for tracking usage
posthog_distinct_id: Default distinct ID for all calls
posthog_properties: Default properties for all calls
posthog_privacy_mode: Default privacy mode for all calls
@@ -146,9 +146,9 @@ class Models:
self._ph_client = posthog_client or setup()
if self._ph_client is None:
raise ValueError("posthog_client is required for PostHog tracking")
raise ValueError("posthog_client is required for Insights tracking")
# Store default PostHog settings
# Store default Insights settings
self._default_distinct_id = posthog_distinct_id
self._default_properties = posthog_properties or {}
self._default_privacy_mode = posthog_privacy_mode
@@ -204,7 +204,7 @@ class Models:
call_privacy_mode: Optional[bool],
call_groups: Optional[Dict[str, Any]],
):
"""Merge call-level PostHog parameters with client defaults."""
"""Merge call-level Insights parameters with client defaults."""
# Use call-level values if provided, otherwise fall back to defaults
distinct_id = (
@@ -242,10 +242,10 @@ class Models:
**kwargs: Any,
):
"""
Generate content using Gemini's API while tracking usage in PostHog.
Generate content using Gemini's API while tracking usage in Insights.
This method signature exactly matches genai.Client().models.generate_content()
with additional PostHog tracking parameters.
with additional Insights tracking parameters.
Args:
model: The model to use (e.g., 'gemini-2.0-flash')
@@ -258,7 +258,7 @@ class Models:
**kwargs: Arguments passed to Gemini's generate_content
"""
# Merge PostHog parameters
# Merge Insights parameters
distinct_id, trace_id, properties, privacy_mode, groups = (
self._merge_posthog_params(
posthog_distinct_id,
@@ -380,7 +380,7 @@ class Models:
capture_streaming_event(self._ph_client, event_data)
def _format_input(self, contents, **kwargs):
"""Format input contents for PostHog tracking"""
"""Format input contents for Insights tracking"""
# Create kwargs dict with contents for merge_system_prompt
input_kwargs = {"contents": contents, **kwargs}
@@ -397,7 +397,7 @@ class Models:
posthog_groups: Optional[Dict[str, Any]] = None,
**kwargs: Any,
):
# Merge PostHog parameters
# Merge Insights parameters
distinct_id, trace_id, properties, privacy_mode, groups = (
self._merge_posthog_params(
posthog_distinct_id,
+19 -19
View File
@@ -13,7 +13,7 @@ except ImportError:
"Please install the Google Gemini SDK to use this feature: 'pip install google-genai'"
)
from posthog import setup
from hanzo_insights import setup
from hanzo_insights.ai.utils import (
call_llm_and_track_usage_async,
capture_streaming_event,
@@ -25,12 +25,12 @@ from hanzo_insights.ai.gemini.gemini_converter import (
format_gemini_streaming_output,
)
from hanzo_insights.ai.sanitization import sanitize_gemini
from hanzo_insights.client import Client as PostHogClient
from hanzo_insights.client import Client as InsightsClient
class AsyncClient:
"""
An async drop-in replacement for genai.Client that automatically sends LLM usage events to PostHog.
An async drop-in replacement for genai.Client that automatically sends LLM usage events to Insights.
Usage:
client = AsyncClient(
@@ -46,7 +46,7 @@ class AsyncClient:
)
"""
_ph_client: PostHogClient
_ph_client: InsightsClient
def __init__(
self,
@@ -57,7 +57,7 @@ class AsyncClient:
location: Optional[str] = None,
debug_config: Optional[Any] = None,
http_options: Optional[Any] = None,
posthog_client: Optional[PostHogClient] = None,
posthog_client: Optional[InsightsClient] = None,
posthog_distinct_id: Optional[str] = None,
posthog_properties: Optional[Dict[str, Any]] = None,
posthog_privacy_mode: bool = False,
@@ -73,7 +73,7 @@ class AsyncClient:
location: GCP location for Vertex AI
debug_config: Debug configuration for the client
http_options: HTTP options for the client
posthog_client: PostHog client for tracking usage
posthog_client: Insights client for tracking usage
posthog_distinct_id: Default distinct ID for all calls (can be overridden per call)
posthog_properties: Default properties for all calls (can be overridden per call)
posthog_privacy_mode: Default privacy mode for all calls (can be overridden per call)
@@ -84,7 +84,7 @@ class AsyncClient:
self._ph_client = posthog_client or setup()
if self._ph_client is None:
raise ValueError("posthog_client is required for PostHog tracking")
raise ValueError("posthog_client is required for Insights tracking")
self.models = AsyncModels(
api_key=api_key,
@@ -105,10 +105,10 @@ class AsyncClient:
class AsyncModels:
"""
Async Models interface that mimics genai.Client().aio.models with PostHog tracking.
Async Models interface that mimics genai.Client().aio.models with Insights tracking.
"""
_ph_client: PostHogClient # Not None after __init__ validation
_ph_client: InsightsClient # Not None after __init__ validation
def __init__(
self,
@@ -119,7 +119,7 @@ class AsyncModels:
location: Optional[str] = None,
debug_config: Optional[Any] = None,
http_options: Optional[Any] = None,
posthog_client: Optional[PostHogClient] = None,
posthog_client: Optional[InsightsClient] = None,
posthog_distinct_id: Optional[str] = None,
posthog_properties: Optional[Dict[str, Any]] = None,
posthog_privacy_mode: bool = False,
@@ -135,7 +135,7 @@ class AsyncModels:
location: GCP location for Vertex AI
debug_config: Debug configuration for the client
http_options: HTTP options for the client
posthog_client: PostHog client for tracking usage
posthog_client: Insights client for tracking usage
posthog_distinct_id: Default distinct ID for all calls
posthog_properties: Default properties for all calls
posthog_privacy_mode: Default privacy mode for all calls
@@ -146,9 +146,9 @@ class AsyncModels:
self._ph_client = posthog_client or setup()
if self._ph_client is None:
raise ValueError("posthog_client is required for PostHog tracking")
raise ValueError("posthog_client is required for Insights tracking")
# Store default PostHog settings
# Store default Insights settings
self._default_distinct_id = posthog_distinct_id
self._default_properties = posthog_properties or {}
self._default_privacy_mode = posthog_privacy_mode
@@ -204,7 +204,7 @@ class AsyncModels:
call_privacy_mode: Optional[bool],
call_groups: Optional[Dict[str, Any]],
):
"""Merge call-level PostHog parameters with client defaults."""
"""Merge call-level Insights parameters with client defaults."""
# Use call-level values if provided, otherwise fall back to defaults
distinct_id = (
@@ -242,10 +242,10 @@ class AsyncModels:
**kwargs: Any,
):
"""
Generate content using Gemini's API while tracking usage in PostHog.
Generate content using Gemini's API while tracking usage in Insights.
This method signature exactly matches genai.Client().aio.models.generate_content()
with additional PostHog tracking parameters.
with additional Insights tracking parameters.
Args:
model: The model to use (e.g., 'gemini-2.0-flash')
@@ -258,7 +258,7 @@ class AsyncModels:
**kwargs: Arguments passed to Gemini's generate_content
"""
# Merge PostHog parameters
# Merge Insights parameters
distinct_id, trace_id, properties, privacy_mode, groups = (
self._merge_posthog_params(
posthog_distinct_id,
@@ -383,7 +383,7 @@ class AsyncModels:
capture_streaming_event(self._ph_client, event_data)
def _format_input(self, contents, **kwargs):
"""Format input contents for PostHog tracking"""
"""Format input contents for Insights tracking"""
# Create kwargs dict with contents for merge_system_prompt
input_kwargs = {"contents": contents, **kwargs}
@@ -400,7 +400,7 @@ class AsyncModels:
posthog_groups: Optional[Dict[str, Any]] = None,
**kwargs: Any,
):
# Merge PostHog parameters
# Merge Insights parameters
distinct_id, trace_id, properties, privacy_mode, groups = (
self._merge_posthog_params(
posthog_distinct_id,
+2 -2
View File
@@ -2,7 +2,7 @@
Gemini-specific conversion utilities.
This module handles the conversion of Gemini API responses and inputs
into standardized formats for PostHog tracking.
into standardized formats for Insights tracking.
"""
from typing import Any, Dict, List, Optional, TypedDict, Union
@@ -362,7 +362,7 @@ def format_gemini_input_with_system(
def format_gemini_input(contents: Any) -> List[FormattedMessage]:
"""
Format Gemini input contents into standardized message format for PostHog tracking.
Format Gemini input contents into standardized message format for Insights tracking.
This function handles various input formats:
- String inputs
+6 -6
View File
@@ -41,7 +41,7 @@ from langchain_core.messages import (
from langchain_core.outputs import ChatGeneration, LLMResult
from pydantic import BaseModel
from posthog import setup
from hanzo_insights import setup
from hanzo_insights.ai.sanitization import sanitize_langchain
from hanzo_insights.ai.utils import get_model_params, with_privacy_mode
from hanzo_insights.client import Client
@@ -80,7 +80,7 @@ class GenerationMetadata(SpanMetadata):
tools: Optional[List[Dict[str, Any]]] = None
"""Tools provided to the model."""
posthog_properties: Optional[Dict[str, Any]] = None
"""PostHog properties of the run."""
"""Insights properties of the run."""
RunMetadata = Union[SpanMetadata, GenerationMetadata]
@@ -89,11 +89,11 @@ RunMetadataStorage = Dict[UUID, RunMetadata]
class CallbackHandler(BaseCallbackHandler):
"""
The PostHog LLM observability callback handler for LangChain.
The Insights LLM observability callback handler for LangChain.
"""
_ph_client: Client
"""PostHog client instance."""
"""Insights client instance."""
_distinct_id: Optional[Union[str, int, UUID]]
"""Distinct ID of the user to associate the trace with."""
@@ -131,12 +131,12 @@ class CallbackHandler(BaseCallbackHandler):
):
"""
Args:
client: PostHog client instance.
client: Insights client instance.
distinct_id: Optional distinct ID of the user to associate the trace with.
trace_id: Optional trace ID to use for the event.
properties: Optional additional metadata to use for the trace.
privacy_mode: Whether to redact the input and output of the trace.
groups: Optional additional PostHog groups to use for the trace.
groups: Optional additional Insights groups to use for the trace.
"""
self._ph_client = client or setup()
self._distinct_id = distinct_id
+14 -14
View File
@@ -24,18 +24,18 @@ from hanzo_insights.ai.openai.openai_converter import (
accumulate_openai_tool_calls,
)
from hanzo_insights.ai.sanitization import sanitize_openai, sanitize_openai_response
from hanzo_insights.client import Client as PostHogClient
from posthog import setup
from hanzo_insights.client import Client as InsightsClient
from hanzo_insights import setup
class OpenAI(openai.OpenAI):
"""
A wrapper around the OpenAI SDK that automatically sends LLM usage events to PostHog.
A wrapper around the OpenAI SDK that automatically sends LLM usage events to Insights.
"""
_ph_client: PostHogClient
_ph_client: InsightsClient
def __init__(self, posthog_client: Optional[PostHogClient] = None, **kwargs):
def __init__(self, posthog_client: Optional[InsightsClient] = None, **kwargs):
"""
Args:
api_key: OpenAI API key.
@@ -67,7 +67,7 @@ class OpenAI(openai.OpenAI):
class WrappedResponses:
"""Wrapper for OpenAI responses that tracks usage in PostHog."""
"""Wrapper for OpenAI responses that tracks usage in Insights."""
def __init__(self, client: OpenAI, original_responses):
self._client = client
@@ -232,7 +232,7 @@ class WrappedResponses:
**kwargs: Any,
):
"""
Parse structured output using OpenAI's 'responses.parse' method, but also track usage in PostHog.
Parse structured output using OpenAI's 'responses.parse' method, but also track usage in Insights.
Args:
posthog_distinct_id: Optional ID to associate with the usage event.
@@ -260,7 +260,7 @@ class WrappedResponses:
class WrappedChat:
"""Wrapper for OpenAI chat that tracks usage in PostHog."""
"""Wrapper for OpenAI chat that tracks usage in Insights."""
def __init__(self, client: OpenAI, original_chat):
self._client = client
@@ -276,7 +276,7 @@ class WrappedChat:
class WrappedCompletions:
"""Wrapper for OpenAI chat completions that tracks usage in PostHog."""
"""Wrapper for OpenAI chat completions that tracks usage in Insights."""
def __init__(self, client: OpenAI, original_completions):
self._client = client
@@ -451,7 +451,7 @@ class WrappedCompletions:
class WrappedEmbeddings:
"""Wrapper for OpenAI embeddings that tracks usage in PostHog."""
"""Wrapper for OpenAI embeddings that tracks usage in Insights."""
def __init__(self, client: OpenAI, original_embeddings):
self._client = client
@@ -471,7 +471,7 @@ class WrappedEmbeddings:
**kwargs: Any,
):
"""
Create an embedding using OpenAI's 'embeddings.create' method, but also track usage in PostHog.
Create an embedding using OpenAI's 'embeddings.create' method, but also track usage in Insights.
Args:
posthog_distinct_id: Optional ID to associate with the usage event.
@@ -535,7 +535,7 @@ class WrappedEmbeddings:
class WrappedBeta:
"""Wrapper for OpenAI beta features that tracks usage in PostHog."""
"""Wrapper for OpenAI beta features that tracks usage in Insights."""
def __init__(self, client: OpenAI, original_beta):
self._client = client
@@ -551,7 +551,7 @@ class WrappedBeta:
class WrappedBetaChat:
"""Wrapper for OpenAI beta chat that tracks usage in PostHog."""
"""Wrapper for OpenAI beta chat that tracks usage in Insights."""
def __init__(self, client: OpenAI, original_beta_chat):
self._client = client
@@ -567,7 +567,7 @@ class WrappedBetaChat:
class WrappedBetaCompletions:
"""Wrapper for OpenAI beta chat completions that tracks usage in PostHog."""
"""Wrapper for OpenAI beta chat completions that tracks usage in Insights."""
def __init__(self, client: OpenAI, original_beta_completions):
self._client = client
+14 -14
View File
@@ -11,7 +11,7 @@ except ImportError:
"Please install the OpenAI SDK to use this feature: 'pip install openai'"
)
from posthog import setup
from hanzo_insights import setup
from hanzo_insights.ai.utils import (
call_llm_and_track_usage_async,
extract_available_tool_calls,
@@ -27,17 +27,17 @@ from hanzo_insights.ai.openai.openai_converter import (
format_openai_streaming_output,
)
from hanzo_insights.ai.sanitization import sanitize_openai, sanitize_openai_response
from hanzo_insights.client import Client as PostHogClient
from hanzo_insights.client import Client as InsightsClient
class AsyncOpenAI(openai.AsyncOpenAI):
"""
An async wrapper around the OpenAI SDK that automatically sends LLM usage events to PostHog.
An async wrapper around the OpenAI SDK that automatically sends LLM usage events to Insights.
"""
_ph_client: PostHogClient
_ph_client: InsightsClient
def __init__(self, posthog_client: Optional[PostHogClient] = None, **kwargs):
def __init__(self, posthog_client: Optional[InsightsClient] = None, **kwargs):
"""
Args:
api_key: OpenAI API key.
@@ -70,7 +70,7 @@ class AsyncOpenAI(openai.AsyncOpenAI):
class WrappedResponses:
"""Async wrapper for OpenAI responses that tracks usage in PostHog."""
"""Async wrapper for OpenAI responses that tracks usage in Insights."""
def __init__(self, client: AsyncOpenAI, original_responses):
self._client = client
@@ -260,7 +260,7 @@ class WrappedResponses:
**kwargs: Any,
):
"""
Parse structured output using OpenAI's 'responses.parse' method, but also track usage in PostHog.
Parse structured output using OpenAI's 'responses.parse' method, but also track usage in Insights.
Args:
posthog_distinct_id: Optional ID to associate with the usage event.
@@ -288,7 +288,7 @@ class WrappedResponses:
class WrappedChat:
"""Async wrapper for OpenAI chat that tracks usage in PostHog."""
"""Async wrapper for OpenAI chat that tracks usage in Insights."""
def __init__(self, client: AsyncOpenAI, original_chat):
self._client = client
@@ -304,7 +304,7 @@ class WrappedChat:
class WrappedCompletions:
"""Async wrapper for OpenAI chat completions that tracks usage in PostHog."""
"""Async wrapper for OpenAI chat completions that tracks usage in Insights."""
def __init__(self, client: AsyncOpenAI, original_completions):
self._client = client
@@ -504,7 +504,7 @@ class WrappedCompletions:
class WrappedEmbeddings:
"""Async wrapper for OpenAI embeddings that tracks usage in PostHog."""
"""Async wrapper for OpenAI embeddings that tracks usage in Insights."""
def __init__(self, client: AsyncOpenAI, original_embeddings):
self._client = client
@@ -525,7 +525,7 @@ class WrappedEmbeddings:
**kwargs: Any,
):
"""
Create an embedding using OpenAI's 'embeddings.create' method, but also track usage in PostHog.
Create an embedding using OpenAI's 'embeddings.create' method, but also track usage in Insights.
Args:
posthog_distinct_id: Optional ID to associate with the usage event.
@@ -590,7 +590,7 @@ class WrappedEmbeddings:
class WrappedBeta:
"""Async wrapper for OpenAI beta features that tracks usage in PostHog."""
"""Async wrapper for OpenAI beta features that tracks usage in Insights."""
def __init__(self, client: AsyncOpenAI, original_beta):
self._client = client
@@ -607,7 +607,7 @@ class WrappedBeta:
class WrappedBetaChat:
"""Async wrapper for OpenAI beta chat that tracks usage in PostHog."""
"""Async wrapper for OpenAI beta chat that tracks usage in Insights."""
def __init__(self, client: AsyncOpenAI, original_beta_chat):
self._client = client
@@ -624,7 +624,7 @@ class WrappedBetaChat:
class WrappedBetaCompletions:
"""Async wrapper for OpenAI beta chat completions that tracks usage in PostHog."""
"""Async wrapper for OpenAI beta chat completions that tracks usage in Insights."""
def __init__(self, client: AsyncOpenAI, original_beta_completions):
self._client = client
+2 -2
View File
@@ -2,7 +2,7 @@
OpenAI-specific conversion utilities.
This module handles the conversion of OpenAI API responses and inputs
into standardized formats for PostHog tracking. It supports both
into standardized formats for Insights tracking. It supports both
Chat Completions API and Responses API formats.
"""
@@ -753,7 +753,7 @@ def format_openai_streaming_input(
api_type: Either "chat" or "responses"
Returns:
Formatted input ready for PostHog tracking
Formatted input ready for Insights tracking
"""
from hanzo_insights.ai.utils import merge_system_prompt
+8 -8
View File
@@ -17,18 +17,18 @@ from hanzo_insights.ai.openai.openai_async import WrappedEmbeddings as AsyncWrap
from hanzo_insights.ai.openai.openai_async import WrappedResponses as AsyncWrappedResponses
from typing import Optional
from hanzo_insights.client import Client as PostHogClient
from posthog import setup
from hanzo_insights.client import Client as InsightsClient
from hanzo_insights import setup
class AzureOpenAI(openai.AzureOpenAI):
"""
A wrapper around the Azure OpenAI SDK that automatically sends LLM usage events to PostHog.
A wrapper around the Azure OpenAI SDK that automatically sends LLM usage events to Insights.
"""
_ph_client: PostHogClient
_ph_client: InsightsClient
def __init__(self, posthog_client: Optional[PostHogClient] = None, **kwargs):
def __init__(self, posthog_client: Optional[InsightsClient] = None, **kwargs):
"""
Args:
api_key: Azure OpenAI API key.
@@ -61,12 +61,12 @@ class AzureOpenAI(openai.AzureOpenAI):
class AsyncAzureOpenAI(openai.AsyncAzureOpenAI):
"""
An async wrapper around the Azure OpenAI SDK that automatically sends LLM usage events to PostHog.
An async wrapper around the Azure OpenAI SDK that automatically sends LLM usage events to Insights.
"""
_ph_client: PostHogClient
_ph_client: InsightsClient
def __init__(self, posthog_client: Optional[PostHogClient] = None, **kwargs):
def __init__(self, posthog_client: Optional[InsightsClient] = None, **kwargs):
"""
Args:
api_key: Azure OpenAI API key.
+10 -10
View File
@@ -14,9 +14,9 @@ except ImportError:
"Please install the OpenAI Agents SDK to use this feature: 'pip install openai-agents'"
)
from hanzo_insights.ai.openai_agents.processor import PostHogTracingProcessor
from hanzo_insights.ai.openai_agents.processor import InsightsTracingProcessor, PostHogTracingProcessor
__all__ = ["PostHogTracingProcessor", "instrument"]
__all__ = ["InsightsTracingProcessor", "PostHogTracingProcessor", "instrument"]
def instrument(
@@ -25,23 +25,23 @@ def instrument(
privacy_mode: bool = False,
groups: Optional[Dict[str, Any]] = None,
properties: Optional[Dict[str, Any]] = None,
) -> PostHogTracingProcessor:
) -> InsightsTracingProcessor:
"""
One-liner to instrument OpenAI Agents SDK with PostHog tracing.
One-liner to instrument OpenAI Agents SDK with Hanzo Insights tracing.
This registers a PostHogTracingProcessor with the OpenAI Agents SDK,
This registers an InsightsTracingProcessor with the OpenAI Agents SDK,
automatically capturing traces, spans, and LLM generations.
Args:
client: Optional PostHog client instance. If not provided, uses the default client.
client: Optional Insights client instance. If not provided, uses the default client.
distinct_id: Optional distinct ID to associate with all traces.
Can also be a callable that takes a trace and returns a distinct ID.
privacy_mode: If True, redacts input/output content from events.
groups: Optional PostHog groups to associate with events.
groups: Optional Insights groups to associate with events.
properties: Optional additional properties to include with all events.
Returns:
PostHogTracingProcessor: The registered processor instance.
InsightsTracingProcessor: The registered processor instance.
Example:
```python
@@ -57,7 +57,7 @@ def instrument(
properties={"environment": "production"}
)
# Now run agents as normal - traces automatically sent to PostHog
# Now run agents as normal - traces automatically sent to Insights
from agents import Agent, Runner
agent = Agent(name="Assistant", instructions="You are helpful.")
result = Runner.run_sync(agent, "Hello!")
@@ -65,7 +65,7 @@ def instrument(
"""
from agents.tracing import add_trace_processor
processor = PostHogTracingProcessor(
processor = InsightsTracingProcessor(
client=client,
distinct_id=distinct_id,
privacy_mode=privacy_mode,
+17 -13
View File
@@ -20,7 +20,7 @@ from agents.tracing.span_data import (
TranscriptionSpanData,
)
from posthog import setup
from hanzo_insights import setup
from hanzo_insights.client import Client
log = logging.getLogger("hanzo_insights")
@@ -53,27 +53,27 @@ def _parse_iso_timestamp(iso_str: Optional[str]) -> Optional[float]:
return None
class PostHogTracingProcessor(TracingProcessor):
class InsightsTracingProcessor(TracingProcessor):
"""
A tracing processor that sends OpenAI Agents SDK traces to PostHog.
A tracing processor that sends OpenAI Agents SDK traces to Hanzo Insights.
This processor implements the TracingProcessor interface from the OpenAI Agents SDK
and maps agent traces, spans, and generations to PostHog's LLM analytics events.
and maps agent traces, spans, and generations to Insights LLM analytics events.
Example:
```python
from agents import Agent, Runner
from agents.tracing import add_trace_processor
from hanzo_insights.ai.openai_agents import PostHogTracingProcessor
from hanzo_insights.ai.openai_agents import InsightsTracingProcessor
# Create and register the processor
processor = PostHogTracingProcessor(
processor = InsightsTracingProcessor(
distinct_id="user@example.com",
privacy_mode=False,
)
add_trace_processor(processor)
# Run agents as normal - traces automatically sent to PostHog
# Run agents as normal - traces automatically sent to Insights
agent = Agent(name="Assistant", instructions="You are helpful.")
result = Runner.run_sync(agent, "Hello!")
```
@@ -88,14 +88,14 @@ class PostHogTracingProcessor(TracingProcessor):
properties: Optional[Dict[str, Any]] = None,
):
"""
Initialize the PostHog tracing processor.
Initialize the Insights tracing processor.
Args:
client: Optional PostHog client instance. If not provided, uses the default client.
client: Optional Insights client instance. If not provided, uses the default client.
distinct_id: Either a string distinct ID or a callable that takes a Trace
and returns a distinct ID. If not provided, uses the trace_id.
privacy_mode: If True, redacts input/output content from events.
groups: Optional PostHog groups to associate with all events.
groups: Optional Insights groups to associate with all events.
properties: Optional additional properties to include with all events.
"""
self._client = client or setup()
@@ -173,7 +173,7 @@ class PostHogTracingProcessor(TracingProcessor):
properties: Dict[str, Any],
distinct_id: Optional[str] = None,
) -> None:
"""Capture an event to PostHog with error handling.
"""Capture an event to Insights with error handling.
Args:
distinct_id: The resolved distinct ID. When the user didn't provide
@@ -199,7 +199,7 @@ class PostHogTracingProcessor(TracingProcessor):
groups=self._groups,
)
except Exception as e:
log.debug(f"Failed to capture PostHog event: {e}")
log.debug(f"Failed to capture Insights event: {e}")
def on_trace_start(self, trace: Trace) -> None:
"""Called when a new trace begins. Stores metadata for spans; the $ai_trace event is emitted in on_trace_end."""
@@ -848,7 +848,7 @@ class PostHogTracingProcessor(TracingProcessor):
self._span_start_times.clear()
self._trace_metadata.clear()
# Flush the PostHog client if possible
# Flush the Insights client if possible
if hasattr(self._client, "flush") and callable(self._client.flush):
self._client.flush()
except Exception as e:
@@ -861,3 +861,7 @@ class PostHogTracingProcessor(TracingProcessor):
self._client.flush()
except Exception as e:
log.debug(f"Error in force_flush: {e}")
# Backward compatibility alias
PostHogTracingProcessor = InsightsTracingProcessor
+24 -24
View File
@@ -1,7 +1,7 @@
"""
Prompt management for PostHog AI SDK.
Prompt management for Hanzo Insights AI SDK.
Fetch and compile LLM prompts from PostHog with caching and fallback support.
Fetch and compile LLM prompts from Insights with caching and fallback support.
"""
import logging
@@ -15,7 +15,7 @@ from hanzo_insights.utils import remove_trailing_slash
log = logging.getLogger("hanzo_insights")
APP_ENDPOINT = "https://us.hanzo_insights.com"
APP_ENDPOINT = "https://us.posthog.com"
DEFAULT_CACHE_TTL_SECONDS = 300 # 5 minutes
PromptVariables = Dict[str, Union[str, int, float, bool]]
@@ -54,24 +54,24 @@ def _is_prompt_api_response(data: Any) -> bool:
class Prompts:
"""
Fetch and compile LLM prompts from PostHog.
Fetch and compile LLM prompts from Insights.
Can be initialized with a PostHog client or with direct options.
Can be initialized with a Insights client or with direct options.
Examples:
```python
from posthog import Posthog
from hanzo_insights import Insights
from hanzo_insights.ai.prompts import Prompts
# With PostHog client
posthog = Posthog('phc_xxx', host='https://us.hanzo_insights.com', personal_api_key='phx_xxx')
prompts = Prompts(posthog)
# With Insights client
client = Insights('phc_xxx', host='https://us.posthog.com', personal_api_key='phx_xxx')
prompts = Prompts(client)
# Or with direct options (no PostHog client needed)
# Or with direct options (no Insights client needed)
prompts = Prompts(
personal_api_key='phx_xxx',
project_api_key='phc_xxx',
host='https://us.hanzo_insights.com',
host='https://us.posthog.com',
)
# Fetch with caching and fallback
@@ -101,10 +101,10 @@ class Prompts:
Initialize Prompts.
Args:
posthog: PostHog client instance (optional if personal_api_key provided)
posthog: Insights client instance (optional if personal_api_key provided)
personal_api_key: Direct personal API key (optional if posthog provided)
project_api_key: Direct project API key (optional if posthog provided)
host: PostHog host (defaults to app endpoint)
host: Insights host (defaults to app endpoint)
default_cache_ttl_seconds: Default cache TTL (defaults to 300)
"""
self._default_cache_ttl_seconds = (
@@ -132,7 +132,7 @@ class Prompts:
version: Optional[int] = None,
) -> str:
"""
Fetch a prompt by name from the PostHog API.
Fetch a prompt by name from the Insights API.
Caching behavior:
1. If cache is fresh, return cached value
@@ -186,7 +186,7 @@ class Prompts:
# 1. Return stale cache (with warning)
if cached is not None:
log.warning(
"[PostHog Prompts] Failed to fetch %s, using stale cache: %s",
"[Insights Prompts] Failed to fetch %s, using stale cache: %s",
prompt_reference,
error,
)
@@ -195,7 +195,7 @@ class Prompts:
# 2. Return fallback (with warning)
if fallback is not None:
log.warning(
"[PostHog Prompts] Failed to fetch %s, using fallback: %s",
"[Insights Prompts] Failed to fetch %s, using fallback: %s",
prompt_reference,
error,
)
@@ -256,7 +256,7 @@ class Prompts:
def _fetch_prompt_from_api(self, name: str, version: Optional[int] = None) -> str:
"""
Fetch prompt from PostHog API.
Fetch prompt from Insights API.
Endpoint:
{host}/api/environments/@current/llm_prompts/name/{encoded_name}/
@@ -275,12 +275,12 @@ class Prompts:
"""
if not self._personal_api_key:
raise Exception(
"[PostHog Prompts] personal_api_key is required to fetch prompts. "
"[Insights Prompts] personal_api_key is required to fetch prompts. "
"Please provide it when initializing the Prompts instance."
)
if not self._project_api_key:
raise Exception(
"[PostHog Prompts] project_api_key is required to fetch prompts. "
"[Insights Prompts] project_api_key is required to fetch prompts. "
"Please provide it when initializing the Prompts instance."
)
@@ -302,28 +302,28 @@ class Prompts:
if not response.ok:
if response.status_code == 404:
raise Exception(f"[PostHog Prompts] {prompt_label} not found")
raise Exception(f"[Insights Prompts] {prompt_label} not found")
if response.status_code == 403:
raise Exception(
f"[PostHog Prompts] Access denied for {prompt_reference}. "
f"[Insights Prompts] Access denied for {prompt_reference}. "
"Check that your personal_api_key has the correct permissions and the LLM prompts feature is enabled."
)
raise Exception(
f"[PostHog Prompts] Failed to fetch {prompt_label}: HTTP {response.status_code}"
f"[Insights Prompts] Failed to fetch {prompt_label}: HTTP {response.status_code}"
)
try:
data = response.json()
except Exception:
raise Exception(
f"[PostHog Prompts] Invalid response format for {prompt_label}"
f"[Insights Prompts] Invalid response format for {prompt_label}"
)
if not _is_prompt_api_response(data):
raise Exception(
f"[PostHog Prompts] Invalid response format for {prompt_label}"
f"[Insights Prompts] Invalid response format for {prompt_label}"
)
return data["prompt"]
+3 -3
View File
@@ -1,5 +1,5 @@
"""
Common type definitions for PostHog AI SDK.
Common type definitions for Insights AI SDK.
These types are used for formatting messages and responses across different AI providers
(Anthropic, OpenAI, Gemini, etc.) to ensure consistency in tracking and data structure.
@@ -41,10 +41,10 @@ FormattedContentItem = Union[
class FormattedMessage(TypedDict):
"""
Standardized message format for PostHog tracking.
Standardized message format for Insights tracking.
Used across all providers to ensure consistent message structure
when sending events to PostHog.
when sending events to Insights.
"""
role: str
+11 -11
View File
@@ -2,7 +2,7 @@ import time
import uuid
from typing import Any, Callable, Dict, List, Optional, cast
from posthog import get_tags, identify_context, new_context, tag, contexts
from hanzo_insights import get_tags, identify_context, new_context, tag, contexts
from hanzo_insights.ai.sanitization import (
sanitize_anthropic,
sanitize_gemini,
@@ -10,7 +10,7 @@ from hanzo_insights.ai.sanitization import (
sanitize_openai,
)
from hanzo_insights.ai.types import FormattedMessage, StreamingEventData, TokenUsage
from hanzo_insights.client import Client as PostHogClient
from hanzo_insights.client import Client as InsightsClient
_TOKEN_PROPERTY_KEYS = frozenset(
@@ -40,7 +40,7 @@ def serialize_raw_usage(raw_usage: Any) -> Optional[Dict[str, Any]]:
Convert raw provider usage objects to JSON-serializable dicts.
Handles Pydantic models (OpenAI/Anthropic) and protobuf-like objects (Gemini)
with a fallback chain to ensure we never pass unserializable objects to PostHog.
with a fallback chain to ensure we never pass unserializable objects to Insights.
Args:
raw_usage: Raw usage object from provider SDK
@@ -320,7 +320,7 @@ def merge_system_prompt(
def call_llm_and_track_usage(
posthog_distinct_id: Optional[str],
ph_client: PostHogClient,
ph_client: InsightsClient,
provider: str,
posthog_trace_id: Optional[str],
posthog_properties: Optional[Dict[str, Any]],
@@ -469,7 +469,7 @@ def call_llm_and_track_usage(
async def call_llm_and_track_usage_async(
posthog_distinct_id: Optional[str],
ph_client: PostHogClient,
ph_client: InsightsClient,
provider: str,
posthog_trace_id: Optional[str],
posthog_properties: Optional[Dict[str, Any]],
@@ -625,14 +625,14 @@ def sanitize_messages(data: Any, provider: str) -> Any:
return data
def with_privacy_mode(ph_client: PostHogClient, privacy_mode: bool, value: Any):
def with_privacy_mode(ph_client: InsightsClient, privacy_mode: bool, value: Any):
if ph_client.privacy_mode or privacy_mode:
return None
return value
def capture_streaming_event(
ph_client: PostHogClient,
ph_client: InsightsClient,
event_data: StreamingEventData,
):
"""
@@ -642,15 +642,15 @@ def capture_streaming_event(
All provider-specific formatting should be done BEFORE calling this function.
The function handles:
- Building PostHog event properties
- Building Insights event properties
- Extracting and adding tools based on provider
- Applying privacy mode
- Adding special token fields (cache, reasoning)
- Provider-specific fields (e.g., OpenAI instructions)
- Sending the event to PostHog
- Sending the event to Insights
Args:
ph_client: PostHog client instance
ph_client: Insights client instance
event_data: Standardized streaming event data containing all necessary information
"""
trace_id = event_data.get("trace_id") or str(uuid.uuid4())
@@ -747,7 +747,7 @@ def capture_streaming_event(
if event_data.get("distinct_id") is None:
event_properties["$process_person_profile"] = False
# Send event to PostHog
# Send event to Insights
if hasattr(ph_client, "capture"):
ph_client.capture(
distinct_id=event_data.get("distinct_id") or trace_id,
+15 -15
View File
@@ -149,15 +149,15 @@ def no_throw(default_return=None):
class Client(object):
"""
This is the SDK reference for the PostHog Python SDK.
This is the SDK reference for the Hanzo Insights Python SDK.
You can learn more about example usage in the [Python SDK documentation](/docs/libraries/python).
You can also follow [Flask](/docs/libraries/flask) and [Django](/docs/libraries/django)
guides to integrate PostHog into your project.
guides to integrate Insights into your project.
Examples:
```python
from posthog import Posthog
posthog = Posthog('<ph_project_api_key>', host='<ph_client_api_host>')
from hanzo_insights import Insights
client = Insights('<ph_project_api_key>', host='<ph_client_api_host>')
hanzo_insights.debug = True
if settings.TEST:
hanzo_insights.disabled = True
@@ -202,7 +202,7 @@ class Client(object):
in_app_modules: list[str] | None = None,
):
"""
Initialize a new PostHog client instance.
Initialize a new Insights client instance.
Args:
project_api_key: The project API key.
@@ -211,9 +211,9 @@ class Client(object):
Examples:
```python
from posthog import Posthog
from hanzo_insights import Insights
posthog = Posthog('<ph_project_api_key>', host='<ph_app_host>')
client = Insights('<ph_project_api_key>', host='<ph_app_host>')
```
Category:
@@ -570,7 +570,7 @@ class Client(object):
self, event: str, **kwargs: Unpack[OptionalCaptureArgs]
) -> Optional[str]:
"""
Captures an event manually. [Learn about capture best practices](https://hanzo_insights.com/docs/product-analytics/capture-events)
Captures an event manually. [Learn about capture best practices](https://insights.hanzo.ai/docs/product-analytics/capture-events)
Args:
event: The event name to capture.
@@ -589,7 +589,7 @@ class Client(object):
```
```python
# Context usage
from posthog import identify_context, new_context
from hanzo_insights import identify_context, new_context
with new_context():
identify_context('distinct_id_of_the_user')
hanzo_insights.capture('user_signed_up')
@@ -1285,7 +1285,7 @@ class Client(object):
self._fetch_feature_flags_from_api()
def _fetch_feature_flags_from_api(self):
"""Fetch feature flags from the PostHog API."""
"""Fetch feature flags from the Insights API."""
try:
# Store old flags to detect changes
old_flags_by_key: dict[str, dict] = self.feature_flags_by_key or {}
@@ -1334,7 +1334,7 @@ class Client(object):
except APIError as e:
if e.status == 401:
self.log.error(
"[FEATURE FLAGS] Error loading feature flags: To use feature flags, please set a valid personal_api_key. More information: https://hanzo_insights.com/docs/api/overview"
"[FEATURE FLAGS] Error loading feature flags: To use feature flags, please set a valid personal_api_key. More information: https://insights.hanzo.ai/docs/api/overview"
)
self.feature_flags = []
self.group_type_mapping = {}
@@ -1348,11 +1348,11 @@ class Client(object):
status=401,
message="You are using a write-only key with feature flags. "
"To use feature flags, please set a personal_api_key "
"More information: https://hanzo_insights.com/docs/api/overview",
"More information: https://insights.hanzo.ai/docs/api/overview",
)
elif e.status == 402:
self.log.warning(
"[FEATURE FLAGS] PostHog feature flags quota limited, resetting feature flag data. Learn more about billing limits at https://hanzo_insights.com/docs/billing/limits-alerts"
"[FEATURE FLAGS] Insights feature flags quota limited, resetting feature flag data. Learn more about billing limits at https://insights.hanzo.ai/docs/billing/limits-alerts"
)
# Reset all feature flag data when quota limited
self.feature_flags = []
@@ -1366,7 +1366,7 @@ class Client(object):
if self.debug:
raise APIError(
status=402,
message="PostHog feature flags quota limited",
message="Insights feature flags quota limited",
)
else:
self.log.error(f"[FEATURE FLAGS] Error loading feature flags: {e}")
@@ -2231,7 +2231,7 @@ class Client(object):
"""Initialize feature flag cache for graceful degradation during service outages.
When enabled, the cache stores flag evaluation results and serves them as fallback
when the PostHog API is unavailable. This ensures your application continues to
when the Insights API is unavailable. This ensures your application continues to
receive flag values even during outages.
Args:
+3 -3
View File
@@ -159,7 +159,7 @@ def new_context(
Category:
Contexts
"""
from posthog import capture_exception
from hanzo_insights import capture_exception
current_context = _get_current_context()
new_context = ContextScope(current_context, fresh, capture_exceptions, client)
@@ -244,7 +244,7 @@ def set_context_session(session_id: str) -> None:
Entering a fresh context will clear the context-level session ID.
Args:
session_id: The session ID to associate with the current context and its children. See https://hanzo_insights.com/docs/data/sessions
session_id: The session ID to associate with the current context and its children. See https://insights.hanzo.ai/docs/data/sessions
Category:
Contexts
@@ -377,7 +377,7 @@ def scoped(fresh: bool = False, capture_exceptions: bool = True):
Args:
fresh: Whether to start with a fresh context (default: False)
capture_exceptions: Whether to capture and track exceptions with posthog error tracking (default: True)
capture_exceptions: Whether to capture and track exceptions with Insights error tracking (default: True)
Example:
@hanzo_insights.scoped()
+1 -1
View File
@@ -8,7 +8,7 @@ from typing import Optional
from dateutil import parser
from dateutil.relativedelta import relativedelta
from posthog import utils
from hanzo_insights import utils
from hanzo_insights.types import FlagValue
from hanzo_insights.utils import convert_to_datetime_aware, is_valid_regex
+5 -5
View File
@@ -9,11 +9,11 @@ functions) to share flag definitions and reduce API calls.
Usage:
from posthog import Posthog
from hanzo_insights import Insights
from hanzo_insights.flag_definition_cache import FlagDefinitionCacheProvider
cache = RedisFlagDefinitionCache(redis_client, "my-team")
posthog = Posthog(
client = Insights(
"<project_api_key>",
personal_api_key="<personal_api_key>",
flag_definition_cache_provider=cache,
@@ -63,7 +63,7 @@ class FlagDefinitionCacheProvider(Protocol):
new definitions from the API. Store the data in your external cache
and release any locks.
4. `shutdown()` - Called when the PostHog client shuts down. Release any
4. `shutdown()` - Called when the Insights client shuts down. Release any
distributed locks and clean up resources.
Error Handling:
@@ -104,7 +104,7 @@ class FlagDefinitionCacheProvider(Protocol):
def on_flag_definitions_received(self, data: FlagDefinitionCacheData) -> None:
"""
Called after successfully receiving new flag definitions from PostHog.
Called after successfully receiving new flag definitions from Insights.
Use this to store the data in your external cache and release any
distributed locks acquired in `should_fetch_flag_definitions()`.
@@ -117,7 +117,7 @@ class FlagDefinitionCacheProvider(Protocol):
def shutdown(self) -> None:
"""
Called when the PostHog client shuts down.
Called when the Insights client shuts down.
Use this to release any distributed locks and clean up resources.
This method is called even if `should_fetch_flag_definitions()`
+47 -37
View File
@@ -1,5 +1,5 @@
from typing import TYPE_CHECKING, cast
from posthog import contexts
from hanzo_insights import contexts
from hanzo_insights.client import Client
try:
@@ -21,25 +21,26 @@ if TYPE_CHECKING:
from typing import Callable, Dict, Any, Optional, Union, Awaitable # noqa: F401
class PosthogContextMiddleware:
class InsightsContextMiddleware:
"""Middleware to automatically track Django requests.
This middleware wraps all calls with a posthog context. It attempts to extract the following from the request headers:
- Session ID, (extracted from `X-POSTHOG-SESSION-ID`)
- Distinct ID, (extracted from `X-POSTHOG-DISTINCT-ID`)
This middleware wraps all calls with an Insights context. It attempts to extract the following from the request headers:
- Session ID, (extracted from `X-INSIGHTS-SESSION-ID` or `X-POSTHOG-SESSION-ID`)
- Distinct ID, (extracted from `X-INSIGHTS-DISTINCT-ID` or `X-POSTHOG-DISTINCT-ID`)
- Request URL as $current_url
- Request Method as $request_method
The context will also auto-capture exceptions and send them to PostHog, unless you disable it by setting
`POSTHOG_MW_CAPTURE_EXCEPTIONS` to `False` in your Django settings. The exceptions are captured using the
global client, unless the setting `POSTHOG_MW_CLIENT` is set to a custom client instance
The context will also auto-capture exceptions and send them to Insights, unless you disable it by setting
`INSIGHTS_MW_CAPTURE_EXCEPTIONS` (or `POSTHOG_MW_CAPTURE_EXCEPTIONS`) to `False` in your Django settings.
The exceptions are captured using the global client, unless the setting `INSIGHTS_MW_CLIENT`
(or `POSTHOG_MW_CLIENT`) is set to a custom client instance.
The middleware behaviour is customisable through 3 additional functions:
- `POSTHOG_MW_EXTRA_TAGS`, which is a Callable[[HttpRequest], Dict[str, Any]] expected to return a dictionary of additional tags to be added to the context.
- `POSTHOG_MW_REQUEST_FILTER`, which is a Callable[[HttpRequest], bool] expected to return `False` if the request should not be tracked.
- `POSTHOG_MW_TAG_MAP`, which is a Callable[[Dict[str, Any]], Dict[str, Any]], which you can use to modify the tags before they're added to the context.
- `INSIGHTS_MW_EXTRA_TAGS` (or `POSTHOG_MW_EXTRA_TAGS`), which is a Callable[[HttpRequest], Dict[str, Any]] expected to return a dictionary of additional tags to be added to the context.
- `INSIGHTS_MW_REQUEST_FILTER` (or `POSTHOG_MW_REQUEST_FILTER`), which is a Callable[[HttpRequest], bool] expected to return `False` if the request should not be tracked.
- `INSIGHTS_MW_TAG_MAP` (or `POSTHOG_MW_TAG_MAP`), which is a Callable[[Dict[str, Any]], Dict[str, Any]], which you can use to modify the tags before they're added to the context.
You can use the `POSTHOG_MW_TAG_MAP` function to remove any default tags you don't want to capture, or override them with your own values.
You can use the `INSIGHTS_MW_TAG_MAP` function to remove any default tags you don't want to capture, or override them with your own values.
Context tags are automatically included as properties on all events captured within a context, including exceptions.
See the context documentation for more information. The extracted distinct ID and session ID, if found, are used to
@@ -66,47 +67,52 @@ class PosthogContextMiddleware:
from django.conf import settings
if hasattr(settings, "POSTHOG_MW_EXTRA_TAGS") and callable(
settings.POSTHOG_MW_EXTRA_TAGS
):
# Support both INSIGHTS_MW_* and legacy POSTHOG_MW_* setting names
def _get_setting(name):
insights_name = f"INSIGHTS_MW_{name}"
posthog_name = f"POSTHOG_MW_{name}"
if hasattr(settings, insights_name):
return getattr(settings, insights_name)
if hasattr(settings, posthog_name):
return getattr(settings, posthog_name)
return None
extra_tags = _get_setting("EXTRA_TAGS")
if extra_tags and callable(extra_tags):
self.extra_tags = cast(
"Optional[Callable[[HttpRequest], Dict[str, Any]]]",
settings.POSTHOG_MW_EXTRA_TAGS,
extra_tags,
)
else:
self.extra_tags = None
if hasattr(settings, "POSTHOG_MW_REQUEST_FILTER") and callable(
settings.POSTHOG_MW_REQUEST_FILTER
):
request_filter = _get_setting("REQUEST_FILTER")
if request_filter and callable(request_filter):
self.request_filter = cast(
"Optional[Callable[[HttpRequest], bool]]",
settings.POSTHOG_MW_REQUEST_FILTER,
request_filter,
)
else:
self.request_filter = None
if hasattr(settings, "POSTHOG_MW_TAG_MAP") and callable(
settings.POSTHOG_MW_TAG_MAP
):
tag_map = _get_setting("TAG_MAP")
if tag_map and callable(tag_map):
self.tag_map = cast(
"Optional[Callable[[Dict[str, Any]], Dict[str, Any]]]",
settings.POSTHOG_MW_TAG_MAP,
tag_map,
)
else:
self.tag_map = None
if hasattr(settings, "POSTHOG_MW_CAPTURE_EXCEPTIONS") and isinstance(
settings.POSTHOG_MW_CAPTURE_EXCEPTIONS, bool
):
self.capture_exceptions = settings.POSTHOG_MW_CAPTURE_EXCEPTIONS
capture_exceptions = _get_setting("CAPTURE_EXCEPTIONS")
if isinstance(capture_exceptions, bool):
self.capture_exceptions = capture_exceptions
else:
self.capture_exceptions = True
if hasattr(settings, "POSTHOG_MW_CLIENT") and isinstance(
settings.POSTHOG_MW_CLIENT, Client
):
self.client = cast("Optional[Client]", settings.POSTHOG_MW_CLIENT)
mw_client = _get_setting("CLIENT")
if isinstance(mw_client, Client):
self.client = cast("Optional[Client]", mw_client)
else:
self.client = None
@@ -125,13 +131,13 @@ class PosthogContextMiddleware:
"""
tags = {}
# Extract session ID from X-POSTHOG-SESSION-ID header
session_id = request.headers.get("X-POSTHOG-SESSION-ID")
# Extract session ID from X-INSIGHTS-SESSION-ID or X-POSTHOG-SESSION-ID header
session_id = request.headers.get("X-INSIGHTS-SESSION-ID") or request.headers.get("X-POSTHOG-SESSION-ID")
if session_id:
contexts.set_context_session(session_id)
# Extract distinct ID from X-POSTHOG-DISTINCT-ID header or request user id
distinct_id = request.headers.get("X-POSTHOG-DISTINCT-ID") or user_id
# Extract distinct ID from X-INSIGHTS-DISTINCT-ID or X-POSTHOG-DISTINCT-ID header or request user id
distinct_id = request.headers.get("X-INSIGHTS-DISTINCT-ID") or request.headers.get("X-POSTHOG-DISTINCT-ID") or user_id
if distinct_id:
contexts.identify_context(distinct_id)
@@ -314,6 +320,10 @@ class PosthogContextMiddleware:
if self.client:
self.client.capture_exception(exception)
else:
from posthog import capture_exception
from hanzo_insights import capture_exception
capture_exception(exception)
# Backward compatibility alias
PosthogContextMiddleware = InsightsContextMiddleware
+8 -8
View File
@@ -137,7 +137,7 @@ def set_socket_options(socket_options: Optional[SocketOptions]) -> None:
Configure socket options for all HTTP connections.
Example:
from posthog import set_socket_options
from hanzo_insights import set_socket_options
set_socket_options([(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)])
"""
global _session, _flags_session, _socket_options
@@ -159,19 +159,19 @@ def disable_connection_reuse() -> None:
_pooling_enabled = False
US_INGESTION_ENDPOINT = "https://us.i.hanzo_insights.com"
EU_INGESTION_ENDPOINT = "https://eu.i.hanzo_insights.com"
US_INGESTION_ENDPOINT = "https://us.i.posthog.com"
EU_INGESTION_ENDPOINT = "https://eu.i.posthog.com"
DEFAULT_HOST = US_INGESTION_ENDPOINT
USER_AGENT = "posthog-python/" + VERSION
USER_AGENT = "hanzo-insights-python/" + VERSION
def determine_server_host(host: Optional[str]) -> str:
"""Determines the server host to use."""
host_or_default = host or DEFAULT_HOST
trimmed_host = remove_trailing_slash(host_or_default)
if trimmed_host in ("https://app.hanzo_insights.com", "https://us.hanzo_insights.com"):
if trimmed_host in ("https://app.posthog.com", "https://us.posthog.com", "https://insights.hanzo.ai"):
return US_INGESTION_ENDPOINT
elif trimmed_host == "https://eu.hanzo_insights.com":
elif trimmed_host == "https://eu.posthog.com":
return EU_INGESTION_ENDPOINT
else:
return host_or_default
@@ -231,7 +231,7 @@ def _process_response(
and "feature_flags" in response["quotaLimited"]
):
log.warning(
"[FEATURE FLAGS] PostHog feature flags quota limited, resetting feature flag data. Learn more about billing limits at https://hanzo_insights.com/docs/billing/limits-alerts"
"[FEATURE FLAGS] Feature flags quota limited, resetting feature flag data. Learn more about billing limits at https://insights.hanzo.ai/docs/billing/limits-alerts"
)
raise QuotaLimitError(res.status_code, "Feature flags quota limited")
return response
@@ -375,7 +375,7 @@ class APIError(Exception):
self.retry_after = retry_after
def __str__(self):
msg = "[PostHog] {0} ({1})"
msg = "[Insights] {0} ({1})"
return msg.format(self.message, self.status)
@@ -3,7 +3,7 @@ from unittest.mock import patch
import pytest
from posthog import identify_context, new_context
from hanzo_insights import identify_context, new_context
try:
from anthropic.types import Message, Usage
@@ -97,7 +97,7 @@ def test_metadata_capture(mock_client):
run_id = uuid.uuid4()
with patch("time.time", return_value=1234567890):
callbacks._set_llm_metadata(
{"kwargs": {"openai_api_base": "https://us.hanzo_insights.com"}},
{"kwargs": {"openai_api_base": "https://us.posthog.com"}},
run_id,
messages=[{"role": "user", "content": "Who won the world series in 2020?"}],
invocation_params={"temperature": 0.5},
@@ -110,7 +110,7 @@ def test_metadata_capture(mock_client):
start_time=1234567890,
model_params={"temperature": 0.5},
provider="hanzo_insights",
base_url="https://us.hanzo_insights.com",
base_url="https://us.posthog.com",
name="test",
end_time=None,
posthog_properties=None,
@@ -1050,7 +1050,7 @@ def test_base_url_retrieval(mock_client):
chain = prompt | ChatOpenAI(
api_key="test",
model="posthog-mini",
base_url="https://test.hanzo_insights.com",
base_url="https://test.posthog.com",
)
callbacks = CallbackHandler(mock_client)
with pytest.raises(Exception):
@@ -1058,7 +1058,7 @@ def test_base_url_retrieval(mock_client):
assert mock_client.capture.call_count == 3
generation_call = mock_client.capture.call_args_list[1][1]
assert generation_call["properties"]["$ai_base_url"] == "https://test.hanzo_insights.com"
assert generation_call["properties"]["$ai_base_url"] == "https://test.posthog.com"
def test_groups(mock_client):
@@ -1253,7 +1253,7 @@ def test_metadata_tools(mock_client):
with patch("time.time", return_value=1234567890):
callbacks._set_llm_metadata(
{"kwargs": {"openai_api_base": "https://us.hanzo_insights.com"}},
{"kwargs": {"openai_api_base": "https://us.posthog.com"}},
run_id,
messages=[{"role": "user", "content": "What's the weather like in SF?"}],
invocation_params={"temperature": 0.5, "tools": tools},
@@ -1266,7 +1266,7 @@ def test_metadata_tools(mock_client):
start_time=1234567890,
model_params={"temperature": 0.5},
provider="hanzo_insights",
base_url="https://us.hanzo_insights.com",
base_url="https://us.posthog.com",
name="test",
tools=tools,
end_time=None,
+10 -10
View File
@@ -36,7 +36,7 @@ class TestPrompts(unittest.TestCase):
self,
personal_api_key="phx_test_key",
project_api_key="phc_test_key",
host="https://us.hanzo_insights.com",
host="https://us.posthog.com",
):
"""Create a mock PostHog client."""
mock = MagicMock()
@@ -65,7 +65,7 @@ class TestPromptsGet(TestPrompts):
call_args = mock_get.call_args
self.assertEqual(
call_args[0][0],
"https://us.hanzo_insights.com/api/environments/@current/llm_prompts/name/test-prompt/?token=phc_test_key",
"https://us.posthog.com/api/environments/@current/llm_prompts/name/test-prompt/?token=phc_test_key",
)
self.assertIn("Authorization", call_args[1]["headers"])
self.assertEqual(
@@ -93,7 +93,7 @@ class TestPromptsGet(TestPrompts):
call_args = mock_get.call_args
self.assertEqual(
call_args[0][0],
"https://us.hanzo_insights.com/api/environments/@current/llm_prompts/name/test-prompt/?token=phc_test_key&version=1",
"https://us.posthog.com/api/environments/@current/llm_prompts/name/test-prompt/?token=phc_test_key&version=1",
)
@patch("hanzo_insights.ai.prompts._get_session")
@@ -347,7 +347,7 @@ class TestPromptsGet(TestPrompts):
mock_get = mock_get_session.return_value.get
mock_get.return_value = MockResponse(json_data=self.mock_prompt_response)
posthog = self.create_mock_posthog(host="https://eu.hanzo_insights.com")
posthog = self.create_mock_posthog(host="https://eu.posthog.com")
prompts = Prompts(posthog)
prompts.get("test-prompt")
@@ -355,9 +355,9 @@ class TestPromptsGet(TestPrompts):
call_args = mock_get.call_args
self.assertTrue(
call_args[0][0].startswith(
"https://eu.hanzo_insights.com/api/environments/@current/llm_prompts/name/test-prompt/?token=phc_test_key"
"https://eu.posthog.com/api/environments/@current/llm_prompts/name/test-prompt/?token=phc_test_key"
),
f"Expected URL to start with 'https://eu.hanzo_insights.com/api/environments/@current/llm_prompts/name/test-prompt/?token=phc_test_key', got {call_args[0][0]}",
f"Expected URL to start with 'https://eu.posthog.com/api/environments/@current/llm_prompts/name/test-prompt/?token=phc_test_key', got {call_args[0][0]}",
)
@patch("hanzo_insights.ai.prompts._get_session")
@@ -427,7 +427,7 @@ class TestPromptsGet(TestPrompts):
call_args = mock_get.call_args
self.assertEqual(
call_args[0][0],
"https://us.hanzo_insights.com/api/environments/@current/llm_prompts/name/prompt%20with%20spaces%2Fand%2Fslashes/?token=phc_test_key",
"https://us.posthog.com/api/environments/@current/llm_prompts/name/prompt%20with%20spaces%2Fand%2Fslashes/?token=phc_test_key",
)
@patch("hanzo_insights.ai.prompts._get_session")
@@ -446,7 +446,7 @@ class TestPromptsGet(TestPrompts):
call_args = mock_get.call_args
self.assertEqual(
call_args[0][0],
"https://us.hanzo_insights.com/api/environments/@current/llm_prompts/name/test-prompt/?token=phc_direct_key",
"https://us.posthog.com/api/environments/@current/llm_prompts/name/test-prompt/?token=phc_direct_key",
)
self.assertEqual(
call_args[1]["headers"]["Authorization"], "Bearer phx_direct_key"
@@ -461,7 +461,7 @@ class TestPromptsGet(TestPrompts):
prompts = Prompts(
personal_api_key="phx_direct_key",
project_api_key="phc_direct_key",
host="https://eu.hanzo_insights.com",
host="https://eu.posthog.com",
)
prompts.get("test-prompt")
@@ -469,7 +469,7 @@ class TestPromptsGet(TestPrompts):
call_args = mock_get.call_args
self.assertEqual(
call_args[0][0],
"https://eu.hanzo_insights.com/api/environments/@current/llm_prompts/name/test-prompt/?token=phc_direct_key",
"https://eu.posthog.com/api/environments/@current/llm_prompts/name/test-prompt/?token=phc_direct_key",
)
@patch("hanzo_insights.ai.prompts._get_session")
+30 -30
View File
@@ -175,7 +175,7 @@ class TestClient(unittest.TestCase):
client = Client(
FAKE_TEST_API_KEY,
on_error=self.set_fail,
host="https://app.hanzo_insights.com",
host="https://app.posthog.com",
)
exception = Exception("test exception")
client.capture_exception(exception, distinct_id="distinct_id")
@@ -230,7 +230,7 @@ class TestClient(unittest.TestCase):
capture_call[1]["properties"]["$exception_list"][0]["stacktrace"][
"frames"
][0]["filename"],
"posthog/test/test_client.py",
"hanzo_insights/test/test_client.py",
)
self.assertEqual(
capture_call[1]["properties"]["$exception_list"][0]["stacktrace"][
@@ -260,7 +260,7 @@ class TestClient(unittest.TestCase):
self.assertFalse(patch_capture.called)
self.assertEqual(
logs.output[0],
"WARNING:posthog:No exception information available",
"WARNING:hanzo_insights:No exception information available",
)
def test_capture_exception_logs_when_enabled(self):
@@ -270,7 +270,7 @@ class TestClient(unittest.TestCase):
Exception("test exception"), distinct_id="distinct_id"
)
self.assertEqual(
logs.output[0], "ERROR:posthog:test exception\nNoneType: None"
logs.output[0], "ERROR:hanzo_insights:test exception\nNoneType: None"
)
@mock.patch("hanzo_insights.client.flags")
@@ -327,7 +327,7 @@ class TestClient(unittest.TestCase):
{
"key": "email",
"type": "person",
"value": "test@hanzo_insights.com",
"value": "test@posthog.com",
"operator": "exact",
}
],
@@ -477,7 +477,7 @@ class TestClient(unittest.TestCase):
self.assertEqual(client.feature_flags_by_key, {})
self.assertEqual(client.group_type_mapping, {})
self.assertEqual(client.cohorts, {})
self.assertIn("PostHog feature flags quota limited", logs.output[0])
self.assertIn("Insights feature flags quota limited", logs.output[0])
@mock.patch("hanzo_insights.client.get")
def test_load_feature_flags_unauthorized(self, patch_get):
@@ -510,7 +510,7 @@ class TestClient(unittest.TestCase):
{
"key": "email",
"type": "person",
"value": "test@hanzo_insights.com",
"value": "test@posthog.com",
"operator": "exact",
}
],
@@ -655,7 +655,7 @@ class TestClient(unittest.TestCase):
self.assertEqual(patch_flags.call_count, 1)
patch_flags.assert_called_with(
"random_key",
"https://us.i.hanzo_insights.com",
"https://us.i.posthog.com",
timeout=3,
distinct_id="distinct_id",
groups={},
@@ -680,7 +680,7 @@ class TestClient(unittest.TestCase):
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(
FAKE_TEST_API_KEY,
host="https://app.hanzo_insights.com",
host="https://app.posthog.com",
on_error=self.set_fail,
personal_api_key=FAKE_TEST_API_KEY,
disable_geoip=True,
@@ -720,7 +720,7 @@ class TestClient(unittest.TestCase):
self.assertEqual(patch_flags.call_count, 1)
patch_flags.assert_called_with(
"random_key",
"https://us.i.hanzo_insights.com",
"https://us.i.posthog.com",
timeout=12,
distinct_id="distinct_id",
groups={},
@@ -1168,7 +1168,7 @@ class TestClient(unittest.TestCase):
msg_uuid = client.capture(
"test_event",
distinct_id="distinct_id",
groups={"company": "id:5", "instance": "app.hanzo_insights.com"},
groups={"company": "id:5", "instance": "app.posthog.com"},
)
self.assertIsNotNone(msg_uuid)
@@ -1180,7 +1180,7 @@ class TestClient(unittest.TestCase):
self.assertEqual(
msg["properties"]["$groups"],
{"company": "id:5", "instance": "app.hanzo_insights.com"},
{"company": "id:5", "instance": "app.posthog.com"},
)
def test_basic_set(self):
@@ -1470,7 +1470,7 @@ class TestClient(unittest.TestCase):
"test_event",
distinct_id="distinct_id",
properties={"$session_id": session_id},
groups={"company": "id:5", "instance": "app.hanzo_insights.com"},
groups={"company": "id:5", "instance": "app.posthog.com"},
)
self.assertIsNotNone(msg_uuid)
@@ -1483,7 +1483,7 @@ class TestClient(unittest.TestCase):
self.assertEqual(msg["properties"]["$session_id"], session_id)
self.assertEqual(
msg["properties"]["$groups"],
{"company": "id:5", "instance": "app.hanzo_insights.com"},
{"company": "id:5", "instance": "app.posthog.com"},
)
def test_session_id_with_anonymous_event(self):
@@ -1920,7 +1920,7 @@ class TestClient(unittest.TestCase):
client.get_feature_flag("random_key", "some_id", disable_geoip=True)
patch_flags.assert_called_with(
"random_key",
"https://us.i.hanzo_insights.com",
"https://us.i.posthog.com",
timeout=3,
distinct_id="some_id",
groups={},
@@ -1936,7 +1936,7 @@ class TestClient(unittest.TestCase):
)
patch_flags.assert_called_with(
"random_key",
"https://us.i.hanzo_insights.com",
"https://us.i.posthog.com",
timeout=3,
distinct_id="feature_enabled_distinct_id",
groups={},
@@ -1950,7 +1950,7 @@ class TestClient(unittest.TestCase):
client.get_all_flags_and_payloads("all_flags_payloads_id")
patch_flags.assert_called_with(
"random_key",
"https://us.i.hanzo_insights.com",
"https://us.i.posthog.com",
timeout=3,
distinct_id="all_flags_payloads_id",
groups={},
@@ -1983,27 +1983,27 @@ class TestClient(unittest.TestCase):
}
client = Client(
FAKE_TEST_API_KEY,
host="http://app2.hanzo_insights.com",
host="http://app2.posthog.com",
on_error=self.set_fail,
disable_geoip=False,
)
client.get_feature_flag(
"random_key",
"some_id",
groups={"company": "id:5", "instance": "app.hanzo_insights.com"},
groups={"company": "id:5", "instance": "app.posthog.com"},
person_properties={"x1": "y1"},
group_properties={"company": {"x": "y"}},
)
patch_flags.assert_called_with(
"random_key",
"http://app2.hanzo_insights.com",
"http://app2.posthog.com",
timeout=3,
distinct_id="some_id",
groups={"company": "id:5", "instance": "app.hanzo_insights.com"},
groups={"company": "id:5", "instance": "app.posthog.com"},
person_properties={"distinct_id": "some_id", "x1": "y1"},
group_properties={
"company": {"$group_key": "id:5", "x": "y"},
"instance": {"$group_key": "app.hanzo_insights.com"},
"instance": {"$group_key": "app.posthog.com"},
},
geoip_disable=False,
device_id=None,
@@ -2014,7 +2014,7 @@ class TestClient(unittest.TestCase):
client.get_feature_flag(
"random_key",
"some_id",
groups={"company": "id:5", "instance": "app.hanzo_insights.com"},
groups={"company": "id:5", "instance": "app.posthog.com"},
person_properties={"distinct_id": "override"},
group_properties={
"company": {
@@ -2024,14 +2024,14 @@ class TestClient(unittest.TestCase):
)
patch_flags.assert_called_with(
"random_key",
"http://app2.hanzo_insights.com",
"http://app2.posthog.com",
timeout=3,
distinct_id="some_id",
groups={"company": "id:5", "instance": "app.hanzo_insights.com"},
groups={"company": "id:5", "instance": "app.posthog.com"},
person_properties={"distinct_id": "override"},
group_properties={
"company": {"$group_key": "group_override"},
"instance": {"$group_key": "app.hanzo_insights.com"},
"instance": {"$group_key": "app.posthog.com"},
},
geoip_disable=False,
device_id=None,
@@ -2045,7 +2045,7 @@ class TestClient(unittest.TestCase):
)
patch_flags.assert_called_with(
"random_key",
"http://app2.hanzo_insights.com",
"http://app2.posthog.com",
timeout=3,
distinct_id="some_id",
groups={},
@@ -2107,7 +2107,7 @@ class TestClient(unittest.TestCase):
expected_call["flag_keys_to_evaluate"] = expected_flag_keys
patch_flags.assert_called_with(
"random_key", "https://us.i.hanzo_insights.com", timeout=3, **expected_call
"random_key", "https://us.i.posthog.com", timeout=3, **expected_call
)
@mock.patch("hanzo_insights.client.flags")
@@ -2131,7 +2131,7 @@ class TestClient(unittest.TestCase):
client.get_feature_flag("random_key", "some_id")
patch_flags.assert_called_with(
"random_key",
"https://us.i.hanzo_insights.com",
"https://us.i.posthog.com",
timeout=3,
distinct_id="some_id",
groups={},
@@ -2151,7 +2151,7 @@ class TestClient(unittest.TestCase):
)
patch_flags.assert_called_with(
"random_key",
"https://us.i.hanzo_insights.com",
"https://us.i.posthog.com",
timeout=3,
distinct_id="some_id",
groups={},
+21 -21
View File
@@ -10,8 +10,8 @@ def test_excepthook(tmpdir):
app.write(
dedent(
"""
from posthog import Posthog
posthog = Posthog('phc_x', host='https://eu.i.hanzo_insights.com', enable_exception_autocapture=True, debug=True, on_error=lambda e, batch: print('error handling batch: ', e, batch))
from hanzo_insights import Posthog
posthog = Posthog('phc_x', host='https://eu.i.posthog.com', enable_exception_autocapture=True, debug=True, on_error=lambda e, batch: print('error handling batch: ', e, batch))
# frame_value = "LOL"
@@ -27,7 +27,7 @@ def test_excepthook(tmpdir):
assert b"ZeroDivisionError" in output
assert b"LOL" in output
assert b"DEBUG:posthog:data uploaded successfully" in output
assert b"DEBUG:hanzo_insights:data uploaded successfully" in output
assert (
b'"$exception_list": [{"mechanism": {"type": "generic", "handled": true}, "module": null, "type": "ZeroDivisionError", "value": "division by zero", "stacktrace": {"frames": [{"platform": "python", "filename": "app.py", "abs_path"'
in output
@@ -40,14 +40,14 @@ def test_code_variables_capture(tmpdir):
dedent(
"""
import os
from posthog import Posthog
from hanzo_insights import Posthog
class UnserializableObject:
pass
posthog = Posthog(
'phc_x',
host='https://eu.i.hanzo_insights.com',
host='https://eu.i.posthog.com',
debug=True,
enable_exception_autocapture=True,
capture_exception_code_variables=True,
@@ -160,12 +160,12 @@ def test_code_variables_context_override(tmpdir):
dedent(
"""
import os
import posthog
from posthog import Posthog
import hanzo_insights
from hanzo_insights import Posthog
posthog_client = Posthog(
'phc_x',
host='https://eu.i.hanzo_insights.com',
host='https://eu.i.posthog.com',
debug=True,
enable_exception_autocapture=True,
capture_exception_code_variables=False,
@@ -205,11 +205,11 @@ def test_code_variables_size_limiter(tmpdir):
dedent(
"""
import os
from posthog import Posthog
from hanzo_insights import Posthog
posthog = Posthog(
'phc_x',
host='https://eu.i.hanzo_insights.com',
host='https://eu.i.posthog.com',
debug=True,
enable_exception_autocapture=True,
capture_exception_code_variables=True,
@@ -299,11 +299,11 @@ def test_code_variables_disabled_capture(tmpdir):
dedent(
"""
import os
from posthog import Posthog
from hanzo_insights import Posthog
posthog = Posthog(
'phc_x',
host='https://eu.i.hanzo_insights.com',
host='https://eu.i.posthog.com',
debug=True,
enable_exception_autocapture=True,
capture_exception_code_variables=False,
@@ -340,12 +340,12 @@ def test_code_variables_enabled_then_disabled_in_context(tmpdir):
dedent(
"""
import os
import posthog
from posthog import Posthog
import hanzo_insights
from hanzo_insights import Posthog
posthog_client = Posthog(
'phc_x',
host='https://eu.i.hanzo_insights.com',
host='https://eu.i.posthog.com',
debug=True,
enable_exception_autocapture=True,
capture_exception_code_variables=True,
@@ -388,7 +388,7 @@ def test_code_variables_repr_fallback(tmpdir):
from datetime import datetime, timedelta
from decimal import Decimal
from fractions import Fraction
from posthog import Posthog
from hanzo_insights import Posthog
class CustomReprClass:
def __repr__(self):
@@ -396,7 +396,7 @@ def test_code_variables_repr_fallback(tmpdir):
posthog = Posthog(
'phc_x',
host='https://eu.i.hanzo_insights.com',
host='https://eu.i.posthog.com',
debug=True,
enable_exception_autocapture=True,
capture_exception_code_variables=True,
@@ -458,11 +458,11 @@ def test_code_variables_too_long_string_value_replaced(tmpdir):
dedent(
"""
import os
from posthog import Posthog
from hanzo_insights import Posthog
posthog = Posthog(
'phc_x',
host='https://eu.i.hanzo_insights.com',
host='https://eu.i.posthog.com',
debug=True,
enable_exception_autocapture=True,
capture_exception_code_variables=True,
@@ -502,11 +502,11 @@ def test_code_variables_too_long_string_in_nested_dict(tmpdir):
dedent(
"""
import os
from posthog import Posthog
from hanzo_insights import Posthog
posthog = Posthog(
'phc_x',
host='https://eu.i.hanzo_insights.com',
host='https://eu.i.posthog.com',
debug=True,
enable_exception_autocapture=True,
capture_exception_code_variables=True,
+13 -13
View File
@@ -2544,7 +2544,7 @@ class TestLocalEvaluation(unittest.TestCase):
client.load_feature_flags()
self.assertEqual(
logs.output[0],
"ERROR:posthog:[FEATURE FLAGS] Error loading feature flags: To use feature flags, please set a valid personal_api_key. More information: https://hanzo_insights.com/docs/api/overview",
"ERROR:hanzo_insights:[FEATURE FLAGS] Error loading feature flags: To use feature flags, please set a valid personal_api_key. More information: https://insights.hanzo.ai/docs/api/overview",
)
client.debug = True
self.assertRaises(APIError, client.load_feature_flags)
@@ -2775,7 +2775,7 @@ class TestLocalEvaluation(unittest.TestCase):
{
"key": "email",
"type": "person",
"value": "test@hanzo_insights.com",
"value": "test@posthog.com",
"operator": "exact",
}
],
@@ -2810,7 +2810,7 @@ class TestLocalEvaluation(unittest.TestCase):
client.get_feature_flag(
"beta-feature",
"test_id",
person_properties={"email": "test@hanzo_insights.com"},
person_properties={"email": "test@posthog.com"},
),
"second-variant",
)
@@ -2838,7 +2838,7 @@ class TestLocalEvaluation(unittest.TestCase):
{
"key": "email",
"type": "person",
"value": "test@hanzo_insights.com",
"value": "test@posthog.com",
"operator": "exact",
}
],
@@ -2851,7 +2851,7 @@ class TestLocalEvaluation(unittest.TestCase):
{
"key": "email",
"type": "person",
"value": "test@hanzo_insights.com",
"value": "test@posthog.com",
"operator": "exact",
}
],
@@ -2886,7 +2886,7 @@ class TestLocalEvaluation(unittest.TestCase):
client.get_feature_flag(
"beta-feature",
"test_id",
person_properties={"email": "test@hanzo_insights.com"},
person_properties={"email": "test@posthog.com"},
),
"second-variant",
)
@@ -2894,7 +2894,7 @@ class TestLocalEvaluation(unittest.TestCase):
client.get_feature_flag(
"beta-feature",
"example_id",
person_properties={"email": "test@hanzo_insights.com"},
person_properties={"email": "test@posthog.com"},
),
"second-variant",
)
@@ -2919,7 +2919,7 @@ class TestLocalEvaluation(unittest.TestCase):
{
"key": "email",
"type": "person",
"value": "test@hanzo_insights.com",
"value": "test@posthog.com",
"operator": "exact",
}
],
@@ -2954,7 +2954,7 @@ class TestLocalEvaluation(unittest.TestCase):
client.get_feature_flag(
"beta-feature",
"test_id",
person_properties={"email": "test@hanzo_insights.com"},
person_properties={"email": "test@posthog.com"},
),
"third-variant",
)
@@ -3109,7 +3109,7 @@ class TestLocalEvaluation(unittest.TestCase):
{
"key": "email",
"type": "person",
"value": "test@hanzo_insights.com",
"value": "test@posthog.com",
"operator": "exact",
}
],
@@ -3149,7 +3149,7 @@ class TestLocalEvaluation(unittest.TestCase):
self.client.get_feature_flag_payload(
"beta-feature",
"test_id",
person_properties={"email": "test@hanzo_insights.com"},
person_properties={"email": "test@posthog.com"},
),
{"a": "json"},
)
@@ -3158,7 +3158,7 @@ class TestLocalEvaluation(unittest.TestCase):
"beta-feature",
"test_id",
match_value="third-variant",
person_properties={"email": "test@hanzo_insights.com"},
person_properties={"email": "test@posthog.com"},
),
{"a": "json"},
)
@@ -3169,7 +3169,7 @@ class TestLocalEvaluation(unittest.TestCase):
"beta-feature",
"test_id",
match_value="first-variant",
person_properties={"email": "test@hanzo_insights.com"},
person_properties={"email": "test@posthog.com"},
),
"some-payload",
)
+8 -8
View File
@@ -1,10 +1,10 @@
import unittest
from posthog import Posthog
from hanzo_insights import Insights
class TestModule(unittest.TestCase):
posthog = None
client = None
def _assert_enqueue_result(self, result):
self.assertEqual(type(result[0]), str)
@@ -14,19 +14,19 @@ class TestModule(unittest.TestCase):
def setUp(self):
self.failed = False
self.posthog = Posthog(
self.client = Insights(
"testsecret", host="http://localhost:8000", on_error=self.failed
)
def test_track(self):
res = self.hanzo_insights.capture("python module event", distinct_id="distinct_id")
res = self.client.capture("python module event", distinct_id="distinct_id")
self._assert_enqueue_result(res)
self.hanzo_insights.flush()
self.client.flush()
def test_alias(self):
res = self.hanzo_insights.alias("previousId", "distinct_id")
res = self.client.alias("previousId", "distinct_id")
self._assert_enqueue_result(res)
self.hanzo_insights.flush()
self.client.flush()
def test_flush(self):
self.hanzo_insights.flush()
self.client.flush()
+22 -22
View File
@@ -72,12 +72,12 @@ class TestRequests(unittest.TestCase):
def test_invalid_request_error(self):
self.assertRaises(
Exception, batch_post, "testsecret", "https://t.hanzo_insights.com", False, "[{]"
Exception, batch_post, "testsecret", "https://t.posthog.com", False, "[{]"
)
def test_invalid_host(self):
self.assertRaises(
Exception, batch_post, "testsecret", "t.hanzo_insights.com/", batch=[]
Exception, batch_post, "testsecret", "t.posthog.com/", batch=[]
)
def test_datetime_serialization(self):
@@ -286,7 +286,7 @@ class TestGet(unittest.TestCase):
call_kwargs = mock_get.call_args[1]
self.assertIn("User-Agent", call_kwargs["headers"])
self.assertTrue(
call_kwargs["headers"]["User-Agent"].startswith("posthog-python/")
call_kwargs["headers"]["User-Agent"].startswith("hanzo-insights-python/")
)
@mock.patch("hanzo_insights.request._session.get")
@@ -332,20 +332,20 @@ class TestGet(unittest.TestCase):
@pytest.mark.parametrize(
"host, expected",
[
("https://t.hanzo_insights.com", "https://t.hanzo_insights.com"),
("https://t.hanzo_insights.com/", "https://t.hanzo_insights.com/"),
("t.hanzo_insights.com", "t.hanzo_insights.com"),
("t.hanzo_insights.com/", "t.hanzo_insights.com/"),
("https://us.hanzo_insights.com.rg.proxy.com", "https://us.hanzo_insights.com.rg.proxy.com"),
("app.hanzo_insights.com", "app.hanzo_insights.com"),
("eu.hanzo_insights.com", "eu.hanzo_insights.com"),
("https://app.hanzo_insights.com", "https://us.i.hanzo_insights.com"),
("https://eu.hanzo_insights.com", "https://eu.i.hanzo_insights.com"),
("https://us.hanzo_insights.com", "https://us.i.hanzo_insights.com"),
("https://app.hanzo_insights.com/", "https://us.i.hanzo_insights.com"),
("https://eu.hanzo_insights.com/", "https://eu.i.hanzo_insights.com"),
("https://us.hanzo_insights.com/", "https://us.i.hanzo_insights.com"),
(None, "https://us.i.hanzo_insights.com"),
("https://t.posthog.com", "https://t.posthog.com"),
("https://t.posthog.com/", "https://t.posthog.com/"),
("t.posthog.com", "t.posthog.com"),
("t.posthog.com/", "t.posthog.com/"),
("https://us.posthog.com.rg.proxy.com", "https://us.posthog.com.rg.proxy.com"),
("app.posthog.com", "app.posthog.com"),
("eu.posthog.com", "eu.posthog.com"),
("https://app.posthog.com", "https://us.i.posthog.com"),
("https://eu.posthog.com", "https://eu.i.posthog.com"),
("https://us.posthog.com", "https://us.i.posthog.com"),
("https://app.posthog.com/", "https://us.i.posthog.com"),
("https://eu.posthog.com/", "https://eu.i.posthog.com"),
("https://us.posthog.com/", "https://us.i.posthog.com"),
(None, "https://us.i.posthog.com"),
],
)
def test_routing_to_custom_host(host, expected):
@@ -428,7 +428,7 @@ class TestFlagsSession(unittest.TestCase):
mock_session.post.return_value = mock_response
mock_get_flags_session.return_value = mock_session
result = flags("test-key", "https://test.hanzo_insights.com", distinct_id="user123")
result = flags("test-key", "https://test.posthog.com", distinct_id="user123")
self.assertEqual(result["featureFlags"]["test-flag"], True)
mock_get_flags_session.assert_called_once()
@@ -453,7 +453,7 @@ class TestFlagsSession(unittest.TestCase):
mock_get_flags_session.return_value = mock_session
with self.assertRaises(QuotaLimitError):
flags("test-key", "https://test.hanzo_insights.com", distinct_id="user123")
flags("test-key", "https://test.posthog.com", distinct_id="user123")
# QuotaLimitError is raised after response is received, not retried
self.assertEqual(mock_session.post.call_count, 1)
@@ -475,7 +475,7 @@ class TestFlagsSessionNetworkRetries(unittest.TestCase):
session = _build_flags_session()
# Get the adapter for https://
adapter = session.get_adapter("https://test.hanzo_insights.com")
adapter = session.get_adapter("https://test.posthog.com")
# Verify retry configuration
retry = adapter.max_retries
@@ -494,7 +494,7 @@ class TestFlagsSessionNetworkRetries(unittest.TestCase):
from hanzo_insights.request import _build_flags_session, RETRY_STATUS_FORCELIST
session = _build_flags_session()
adapter = session.get_adapter("https://test.hanzo_insights.com")
adapter = session.get_adapter("https://test.posthog.com")
retry = adapter.max_retries
# Verify the status codes that trigger retries
@@ -521,7 +521,7 @@ class TestFlagsSessionNetworkRetries(unittest.TestCase):
from hanzo_insights.request import _build_flags_session
session = _build_flags_session()
adapter = session.get_adapter("https://test.hanzo_insights.com")
adapter = session.get_adapter("https://test.posthog.com")
retry = adapter.max_retries
self.assertEqual(
@@ -2,7 +2,7 @@ import unittest
from parameterized import parameterized
from posthog import utils
from hanzo_insights import utils
class TestSizeLimitedDict(unittest.TestCase):
+1 -1
View File
@@ -13,7 +13,7 @@ from parameterized import parameterized
from pydantic import BaseModel
from pydantic.v1 import BaseModel as BaseModelV1
from posthog import utils
from hanzo_insights import utils
from hanzo_insights.types import FeatureFlagResult
TEST_API_KEY = "kOOlRy2QlMY9jHZQv0bKz0FZyazBUoY8Arj0lFVNjs4"
+3
View File
@@ -0,0 +1,3 @@
# Convenience re-export so `from insights import Insights` works.
from hanzo_insights import * # noqa: F401, F403
from hanzo_insights import Insights, Posthog, Client # noqa: F401
@@ -125,5 +125,5 @@ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
# PostHog settings for testing
POSTHOG_API_KEY = "test-key"
POSTHOG_HOST = "https://app.hanzo_insights.com"
POSTHOG_HOST = "https://app.posthog.com"
POSTHOG_MW_CAPTURE_EXCEPTIONS = True
+1
View File
@@ -91,6 +91,7 @@ packages = [
"hanzo_insights.test.ai",
"hanzo_insights.test.ai.openai_agents",
"hanzo_insights.integrations",
"insights",
]
[tool.pytest.ini_options]
+2 -2
View File
@@ -56,9 +56,9 @@ setup(
# Basic fields for backward compatibility
url="https://github.com/posthog/posthog-python",
author="Posthog",
author_email="hey@hanzo_insights.com",
author_email="hey@hanzo.ai",
maintainer="PostHog",
maintainer_email="hey@hanzo_insights.com",
maintainer_email="hey@hanzo.ai",
license="MIT License",
description="Integrate PostHog into any python application.",
long_description=long_description,