chore: rename package from hanzoanalytics to hanzo-insights

Package name: hanzo-insights (import as hanzo_insights)
This commit is contained in:
Hanzo Dev
2026-03-06 22:44:19 -08:00
parent f4cbdf28c4
commit 98a2ca443c
80 changed files with 834 additions and 834 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ Constants for PostHog Python SDK documentation generation.
"""
from typing import Dict, Union
from hanzoanalytics.version import VERSION
from hanzo_insights.version import VERSION
# Documentation generation metadata
DOCUMENTATION_METADATA = {
+8 -8
View File
@@ -339,10 +339,10 @@ def generate_sdk_documentation():
# Import PostHog components
import posthog
from hanzoanalytics.client import Client
import hanzoanalytics.types as types_module
import hanzoanalytics.args as args_module
from hanzoanalytics.version import VERSION
from hanzo_insights.client import Client
import hanzo_insights.types as types_module
import hanzo_insights.args as args_module
from hanzo_insights.version import VERSION
# Main SDK info
sdk_info = {
@@ -357,7 +357,7 @@ def generate_sdk_documentation():
# Collect types
types_list = []
# Types from hanzoanalytics.types
# Types from hanzo_insights.types
for name in dir(types_module):
obj = getattr(types_module, name)
if inspect.isclass(obj) and not name.startswith("_"):
@@ -367,7 +367,7 @@ def generate_sdk_documentation():
except Exception as e:
print(f"Error analyzing type {name}: {e}")
# Types from hanzoanalytics.args
# Types from hanzo_insights.args
for name in dir(args_module):
obj = getattr(args_module, name)
if inspect.isclass(obj) and not name.startswith("_"):
@@ -394,7 +394,7 @@ def generate_sdk_documentation():
client_class["title"] = "PostHog"
classes_list.append(client_class)
# Global module functions (functions callable as hanzoanalytics.function_name)
# Global module functions (functions callable as hanzo_insights.function_name)
global_functions = []
for func_name in dir(posthog):
# Skip private functions and non-callables
@@ -407,7 +407,7 @@ def generate_sdk_documentation():
if (
func_name not in ["Client", "Posthog"]
and hasattr(func, "__module__")
and func.__module__ == "hanzoanalytics"
and func.__module__ == "hanzo_insights"
):
try:
func_info = analyze_function(func, func_name)
+73 -73
View File
@@ -43,16 +43,16 @@ if not project_key:
exit(1)
# Configure PostHog with credentials
hanzoanalytics.debug = False
hanzoanalytics.api_key = project_key
hanzoanalytics.project_api_key = project_key
hanzoanalytics.host = host
hanzoanalytics.poll_interval = 10
hanzo_insights.debug = False
hanzo_insights.api_key = project_key
hanzo_insights.project_api_key = project_key
hanzo_insights.host = host
hanzo_insights.poll_interval = 10
# Check if personal API key is available for local evaluation
local_eval_available = bool(personal_api_key)
if personal_api_key:
hanzoanalytics.personal_api_key = personal_api_key
hanzo_insights.personal_api_key = personal_api_key
print("🔑 PostHog Configuration:")
print(f" Project API Key: {project_key[:9]}...")
@@ -79,11 +79,11 @@ if choice == "1":
print("IDENTIFY AND CAPTURE EXAMPLES")
print("=" * 60)
hanzoanalytics.debug = True
hanzo_insights.debug = True
# Capture an event
print("📊 Capturing events...")
hanzoanalytics.capture(
hanzo_insights.capture(
"event",
distinct_id="distinct_id",
properties={"property1": "value", "property2": "value"},
@@ -92,14 +92,14 @@ if choice == "1":
# Alias a previous distinct id with a new one
print("🔗 Creating alias...")
hanzoanalytics.alias("distinct_id", "new_distinct_id")
hanzo_insights.alias("distinct_id", "new_distinct_id")
hanzoanalytics.capture(
hanzo_insights.capture(
"event2",
distinct_id="new_distinct_id",
properties={"property1": "value", "property2": "value"},
)
hanzoanalytics.capture(
hanzo_insights.capture(
"event-with-groups",
distinct_id="new_distinct_id",
properties={"property1": "value", "property2": "value"},
@@ -108,28 +108,28 @@ if choice == "1":
# Add properties to the person
print("👤 Identifying user...")
hanzoanalytics.set(
hanzo_insights.set(
distinct_id="new_distinct_id", properties={"email": "something@something.com"}
)
# Add properties to a group
print("🏢 Identifying group...")
hanzoanalytics.group_identify("company", "id:5", {"employees": 11})
hanzo_insights.group_identify("company", "id:5", {"employees": 11})
# Properties set only once to the person
print("🔒 Setting properties once...")
hanzoanalytics.set_once(
hanzo_insights.set_once(
distinct_id="new_distinct_id", properties={"self_serve_signup": True}
)
# This will not change the property (because it was already set)
hanzoanalytics.set_once(
hanzo_insights.set_once(
distinct_id="new_distinct_id", properties={"self_serve_signup": False}
)
print("🔄 Updating properties...")
hanzoanalytics.set(distinct_id="new_distinct_id", properties={"current_browser": "Chrome"})
hanzoanalytics.set(
hanzo_insights.set(distinct_id="new_distinct_id", properties={"current_browser": "Chrome"})
hanzo_insights.set(
distinct_id="new_distinct_id", properties={"current_browser": "Firefox"}
)
@@ -139,43 +139,43 @@ elif choice == "2":
print(
" Set POSTHOG_PERSONAL_API_KEY environment variable to run this example."
)
hanzoanalytics.shutdown()
hanzo_insights.shutdown()
exit(1)
print("\n" + "=" * 60)
print("FEATURE FLAG LOCAL EVALUATION EXAMPLES")
print("=" * 60)
hanzoanalytics.debug = True
hanzo_insights.debug = True
print("🏁 Testing basic feature flags...")
print(
f"beta-feature for 'distinct_id': {hanzoanalytics.feature_enabled('beta-feature', 'distinct_id')}"
f"beta-feature for 'distinct_id': {hanzo_insights.feature_enabled('beta-feature', 'distinct_id')}"
)
print(
f"beta-feature for 'new_distinct_id': {hanzoanalytics.feature_enabled('beta-feature', 'new_distinct_id')}"
f"beta-feature for 'new_distinct_id': {hanzo_insights.feature_enabled('beta-feature', 'new_distinct_id')}"
)
print(
f"beta-feature with groups: {hanzoanalytics.feature_enabled('beta-feature-groups', 'distinct_id', groups={'company': 'id:5'})}"
f"beta-feature with groups: {hanzo_insights.feature_enabled('beta-feature-groups', 'distinct_id', groups={'company': 'id:5'})}"
)
print("\n🌍 Testing location-based flags...")
# Assume test-flag has `City Name = Sydney` as a person property set
print(
f"Sydney user: {hanzoanalytics.feature_enabled('test-flag', 'random_id_12345', person_properties={'$geoip_city_name': 'Sydney'})}"
f"Sydney user: {hanzo_insights.feature_enabled('test-flag', 'random_id_12345', person_properties={'$geoip_city_name': 'Sydney'})}"
)
print(
f"Sydney user (local only): {hanzoanalytics.feature_enabled('test-flag', 'distinct_id_random_22', person_properties={'$geoip_city_name': 'Sydney'}, only_evaluate_locally=True)}"
f"Sydney user (local only): {hanzo_insights.feature_enabled('test-flag', 'distinct_id_random_22', person_properties={'$geoip_city_name': 'Sydney'}, only_evaluate_locally=True)}"
)
print("\n📋 Getting all flags...")
print(f"All flags: {hanzoanalytics.get_all_flags('distinct_id_random_22')}")
print(f"All flags: {hanzo_insights.get_all_flags('distinct_id_random_22')}")
print(
f"All flags (local): {hanzoanalytics.get_all_flags('distinct_id_random_22', only_evaluate_locally=True)}"
f"All flags (local): {hanzo_insights.get_all_flags('distinct_id_random_22', only_evaluate_locally=True)}"
)
print(
f"All flags with properties: {hanzoanalytics.get_all_flags('distinct_id_random_22', person_properties={'$geoip_city_name': 'Sydney'}, only_evaluate_locally=True)}"
f"All flags with properties: {hanzo_insights.get_all_flags('distinct_id_random_22', person_properties={'$geoip_city_name': 'Sydney'}, only_evaluate_locally=True)}"
)
elif choice == "3":
@@ -183,22 +183,22 @@ elif choice == "3":
print("FEATURE FLAG PAYLOAD EXAMPLES")
print("=" * 60)
hanzoanalytics.debug = True
hanzo_insights.debug = True
print("📦 Testing feature flag payloads...")
print(
f"beta-feature payload: {hanzoanalytics.get_feature_flag_payload('beta-feature', 'distinct_id')}"
f"beta-feature payload: {hanzo_insights.get_feature_flag_payload('beta-feature', 'distinct_id')}"
)
print(
f"All flags and payloads: {hanzoanalytics.get_all_flags_and_payloads('distinct_id')}"
f"All flags and payloads: {hanzo_insights.get_all_flags_and_payloads('distinct_id')}"
)
print(
f"Remote config payload: {hanzoanalytics.get_remote_config_payload('encrypted_payload_flag_key')}"
f"Remote config payload: {hanzo_insights.get_remote_config_payload('encrypted_payload_flag_key')}"
)
# Get feature flag result with all details (enabled, variant, payload, key, reason)
print("\n🔍 Getting detailed flag result...")
result = hanzoanalytics.get_feature_flag_result("beta-feature", "distinct_id")
result = hanzo_insights.get_feature_flag_result("beta-feature", "distinct_id")
if result:
print(f"Flag key: {result.key}")
print(f"Flag enabled: {result.enabled}")
@@ -214,7 +214,7 @@ elif choice == "4":
print(
" Set POSTHOG_PERSONAL_API_KEY environment variable to run this example."
)
hanzoanalytics.shutdown()
hanzo_insights.shutdown()
exit(1)
print("\n" + "=" * 60)
@@ -234,10 +234,10 @@ elif choice == "4":
print(" - Rollout: 100%")
print("")
hanzoanalytics.debug = True
hanzo_insights.debug = True
# Test @example.com user (should satisfy dependency if flags exist)
result1 = hanzoanalytics.feature_enabled(
result1 = hanzo_insights.feature_enabled(
"test-flag-dependency",
"example_user",
person_properties={"email": "user@example.com"},
@@ -246,7 +246,7 @@ elif choice == "4":
print(f"✅ @example.com user (test-flag-dependency): {result1}")
# Test non-example.com user (dependency should not be satisfied)
result2 = hanzoanalytics.feature_enabled(
result2 = hanzo_insights.feature_enabled(
"test-flag-dependency",
"regular_user",
person_properties={"email": "user@other.com"},
@@ -255,13 +255,13 @@ elif choice == "4":
print(f"❌ Regular user (test-flag-dependency): {result2}")
# Test beta-feature directly for comparison
beta1 = hanzoanalytics.feature_enabled(
beta1 = hanzo_insights.feature_enabled(
"beta-feature",
"example_user",
person_properties={"email": "user@example.com"},
only_evaluate_locally=True,
)
beta2 = hanzoanalytics.feature_enabled(
beta2 = hanzo_insights.feature_enabled(
"beta-feature",
"regular_user",
person_properties={"email": "user@other.com"},
@@ -303,7 +303,7 @@ elif choice == "4":
print("")
# Test pineapple -> blue -> breaking-bad chain
dependent_result3 = hanzoanalytics.get_feature_flag(
dependent_result3 = hanzo_insights.get_feature_flag(
"multivariate-root-flag",
"regular_user",
person_properties={"email": "pineapple@example.com"},
@@ -317,7 +317,7 @@ elif choice == "4":
print("'multivariate-root-flag' with email pineapple@example.com succeeded")
# Test mango -> red -> the-wire chain
dependent_result4 = hanzoanalytics.get_feature_flag(
dependent_result4 = hanzo_insights.get_feature_flag(
"multivariate-root-flag",
"regular_user",
person_properties={"email": "mango@example.com"},
@@ -336,19 +336,19 @@ elif choice == "4":
("pineapple@example.com", ["pineapple", "blue", "breaking-bad"]),
("mango@example.com", ["mango", "red", "the-wire"]),
]:
leaf = hanzoanalytics.get_feature_flag(
leaf = hanzo_insights.get_feature_flag(
"multivariate-leaf-flag",
"regular_user",
person_properties={"email": email},
only_evaluate_locally=True,
)
intermediate = hanzoanalytics.get_feature_flag(
intermediate = hanzo_insights.get_feature_flag(
"multivariate-intermediate-flag",
"regular_user",
person_properties={"email": email},
only_evaluate_locally=True,
)
root = hanzoanalytics.get_feature_flag(
root = hanzo_insights.get_feature_flag(
"multivariate-root-flag",
"regular_user",
person_properties={"email": email},
@@ -373,7 +373,7 @@ elif choice == "5":
print("CONTEXT MANAGEMENT AND TAGGING EXAMPLES")
print("=" * 60)
hanzoanalytics.debug = True
hanzo_insights.debug = True
print("🏷️ Testing context management...")
print(
@@ -384,12 +384,12 @@ elif choice == "5":
# and tagged with the context tags. Other events captured will also be tagged with the context tags. By default,
# the new context inherits tags from the parent context.
try:
with hanzoanalytics.new_context():
hanzoanalytics.tag("transaction_id", "abc123")
hanzoanalytics.tag("some_arbitrary_value", {"tags": "can be dicts"})
with hanzo_insights.new_context():
hanzo_insights.tag("transaction_id", "abc123")
hanzo_insights.tag("some_arbitrary_value", {"tags": "can be dicts"})
# This event will be captured with the tags set above
hanzoanalytics.capture("order_processed")
hanzo_insights.capture("order_processed")
print("✅ Event captured with inherited context tags")
# This exception will be captured with the tags set above
# raise Exception("Order processing failed")
@@ -398,30 +398,30 @@ elif choice == "5":
# Use fresh=True to start with a clean context (no inherited tags)
try:
with hanzoanalytics.new_context(fresh=True):
hanzoanalytics.tag("session_id", "xyz789")
with hanzo_insights.new_context(fresh=True):
hanzo_insights.tag("session_id", "xyz789")
# Only session_id tag will be present, no inherited tags
hanzoanalytics.capture("session_event")
hanzo_insights.capture("session_event")
print("✅ Event captured with fresh context tags")
# raise Exception("Session handling failed")
except Exception as e:
print(f"Exception captured: {e}")
# You can also use the `@hanzoanalytics.scoped()` decorator to enter a new context.
# You can also use the `@hanzo_insights.scoped()` decorator to enter a new context.
# By default, it inherits tags from the parent context
@hanzoanalytics.scoped()
@hanzo_insights.scoped()
def process_order(order_id):
hanzoanalytics.tag("order_id", order_id)
hanzoanalytics.capture("order_step_completed")
hanzo_insights.tag("order_id", order_id)
hanzo_insights.capture("order_step_completed")
print(f"✅ Order {order_id} processed with scoped context")
# Exception will be captured and tagged automatically
# raise Exception("Order processing failed")
# Use fresh=True to start with a clean context (no inherited tags)
@hanzoanalytics.scoped(fresh=True)
@hanzo_insights.scoped(fresh=True)
def process_payment(payment_id):
hanzoanalytics.tag("payment_id", payment_id)
hanzoanalytics.capture("payment_processed")
hanzo_insights.tag("payment_id", payment_id)
hanzo_insights.capture("payment_processed")
print(f"✅ Payment {payment_id} processed with fresh scoped context")
# Only payment_id tag will be present, no inherited tags
# raise Exception("Payment processing failed")
@@ -436,18 +436,18 @@ elif choice == "6":
# Run example 1
print(f"\n{'🔸' * 20} IDENTIFY AND CAPTURE {'🔸' * 20}")
hanzoanalytics.debug = True
hanzo_insights.debug = True
print("📊 Capturing events...")
hanzoanalytics.capture(
hanzo_insights.capture(
"event",
distinct_id="distinct_id",
properties={"property1": "value", "property2": "value"},
send_feature_flags=True,
)
print("🔗 Creating alias...")
hanzoanalytics.alias("distinct_id", "new_distinct_id")
hanzo_insights.alias("distinct_id", "new_distinct_id")
print("👤 Identifying user...")
hanzoanalytics.set(
hanzo_insights.set(
distinct_id="new_distinct_id", properties={"email": "something@something.com"}
)
@@ -455,27 +455,27 @@ elif choice == "6":
if local_eval_available:
print(f"\n{'🔸' * 20} FEATURE FLAGS {'🔸' * 20}")
print("🏁 Testing basic feature flags...")
print(f"beta-feature: {hanzoanalytics.feature_enabled('beta-feature', 'distinct_id')}")
print(f"beta-feature: {hanzo_insights.feature_enabled('beta-feature', 'distinct_id')}")
print(
f"Sydney user: {hanzoanalytics.feature_enabled('test-flag', 'random_id_12345', person_properties={'$geoip_city_name': 'Sydney'})}"
f"Sydney user: {hanzo_insights.feature_enabled('test-flag', 'random_id_12345', person_properties={'$geoip_city_name': 'Sydney'})}"
)
# Run example 3
print(f"\n{'🔸' * 20} PAYLOADS {'🔸' * 20}")
print("📦 Testing payloads...")
print(f"Payload: {hanzoanalytics.get_feature_flag_payload('beta-feature', 'distinct_id')}")
print(f"Payload: {hanzo_insights.get_feature_flag_payload('beta-feature', 'distinct_id')}")
# Run example 4 (requires local evaluation)
if local_eval_available:
print(f"\n{'🔸' * 20} FLAG DEPENDENCIES {'🔸' * 20}")
print("🔗 Testing flag dependencies...")
result1 = hanzoanalytics.feature_enabled(
result1 = hanzo_insights.feature_enabled(
"test-flag-dependency",
"demo_user",
person_properties={"email": "user@example.com"},
only_evaluate_locally=True,
)
result2 = hanzoanalytics.feature_enabled(
result2 = hanzo_insights.feature_enabled(
"test-flag-dependency",
"demo_user2",
person_properties={"email": "user@other.com"},
@@ -486,23 +486,23 @@ elif choice == "6":
# Run example 5
print(f"\n{'🔸' * 20} CONTEXT MANAGEMENT {'🔸' * 20}")
print("🏷️ Testing context management...")
with hanzoanalytics.new_context():
hanzoanalytics.tag("demo_run", "all_examples")
hanzoanalytics.capture("demo_completed")
with hanzo_insights.new_context():
hanzo_insights.tag("demo_run", "all_examples")
hanzo_insights.capture("demo_completed")
print("✅ Demo completed with context tags")
elif choice == "7":
print("👋 Goodbye!")
hanzoanalytics.shutdown()
hanzo_insights.shutdown()
exit()
else:
print("❌ Invalid choice. Please run again and select 1-7.")
hanzoanalytics.shutdown()
hanzo_insights.shutdown()
exit()
print("\n" + "=" * 60)
print("✅ Example completed!")
print("=" * 60)
hanzoanalytics.shutdown()
hanzo_insights.shutdown()
+5 -5
View File
@@ -6,10 +6,10 @@ Simple test script for PostHog remote config endpoint.
import posthog
# Initialize PostHog client
hanzoanalytics.api_key = "phc_..."
hanzoanalytics.personal_api_key = "phs_..." # or "phx_..."
hanzoanalytics.host = "http://localhost:8000" # or "https://us.hanzoanalytics.com"
hanzoanalytics.debug = True
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.debug = True
def test_remote_config():
@@ -21,7 +21,7 @@ def test_remote_config():
try:
# Get remote config payload
payload = hanzoanalytics.get_remote_config_payload(flag_key)
payload = hanzo_insights.get_remote_config_payload(flag_key)
print(f"✅ Success! Remote config payload for '{flag_key}': {payload}")
except Exception as e:
@@ -3,66 +3,66 @@ from typing import Any, Callable, Dict, Optional # noqa: F401
from typing_extensions import Unpack
from hanzoanalytics.args import ExceptionArg, OptionalCaptureArgs, OptionalSetArgs
from hanzoanalytics.client import Client
from hanzoanalytics.contexts import (
from hanzo_insights.args import ExceptionArg, OptionalCaptureArgs, OptionalSetArgs
from hanzo_insights.client import Client
from hanzo_insights.contexts import (
identify_context as inner_identify_context,
)
from hanzoanalytics.contexts import (
from hanzo_insights.contexts import (
new_context as inner_new_context,
)
from hanzoanalytics.contexts import (
from hanzo_insights.contexts import (
scoped as inner_scoped,
)
from hanzoanalytics.contexts import (
from hanzo_insights.contexts import (
set_capture_exception_code_variables_context as inner_set_capture_exception_code_variables_context,
)
from hanzoanalytics.contexts import (
from hanzo_insights.contexts import (
set_code_variables_ignore_patterns_context as inner_set_code_variables_ignore_patterns_context,
)
from hanzoanalytics.contexts import (
from hanzo_insights.contexts import (
set_code_variables_mask_patterns_context as inner_set_code_variables_mask_patterns_context,
)
from hanzoanalytics.contexts import (
from hanzo_insights.contexts import (
set_context_device_id as inner_set_context_device_id,
)
from hanzoanalytics.contexts import (
from hanzo_insights.contexts import (
set_context_session as inner_set_context_session,
)
from hanzoanalytics.contexts import (
from hanzo_insights.contexts import (
tag as inner_tag,
)
from hanzoanalytics.contexts import (
from hanzo_insights.contexts import (
get_tags as inner_get_tags,
)
from hanzoanalytics.exception_utils import (
from hanzo_insights.exception_utils import (
DEFAULT_CODE_VARIABLES_IGNORE_PATTERNS,
DEFAULT_CODE_VARIABLES_MASK_PATTERNS,
)
from hanzoanalytics.feature_flags import (
from hanzo_insights.feature_flags import (
InconclusiveMatchError as InconclusiveMatchError,
)
from hanzoanalytics.feature_flags import (
from hanzo_insights.feature_flags import (
RequiresServerEvaluation as RequiresServerEvaluation,
)
from hanzoanalytics.flag_definition_cache import (
from hanzo_insights.flag_definition_cache import (
FlagDefinitionCacheData as FlagDefinitionCacheData,
FlagDefinitionCacheProvider as FlagDefinitionCacheProvider,
)
from hanzoanalytics.request import (
from hanzo_insights.request import (
disable_connection_reuse as disable_connection_reuse,
enable_keep_alive as enable_keep_alive,
set_socket_options as set_socket_options,
SocketOptions as SocketOptions,
)
from hanzoanalytics.types import (
from hanzo_insights.types import (
FeatureFlag,
FlagsAndPayloads,
)
from hanzoanalytics.types import (
from hanzo_insights.types import (
FeatureFlagResult as FeatureFlagResult,
)
from hanzoanalytics.version import VERSION
from hanzo_insights.version import VERSION
__version__ = VERSION
@@ -263,7 +263,7 @@ in_app_modules = None # type: Optional[list[str]]
# NOTE - this and following functions take unpacked kwargs because we needed to make
# it impossible to write `hanzoanalytics.capture(distinct-id, event-name)` - basically, to enforce
# it impossible to write `hanzo_insights.capture(distinct-id, event-name)` - basically, to enforce
# the breaking change made between 5.3.0 and 6.0.0. This decision can be unrolled in later
# versions, without a breaking change, to get back the type information in function signatures
def capture(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str]:
@@ -479,7 +479,7 @@ def capture_exception(
exception: The exception to capture. If not provided, the current exception is captured via `sys.exc_info()`
Details:
Capture exception is idempotent - if it is called twice with the same exception instance, only a occurrence will be tracked in hanzoanalytics. This is because, generally, contexts will cause exceptions to be captured automatically. However, to ensure you track an exception, if you catch and do not re-raise it, capturing it manually is recommended, unless you are certain it will have crossed a context boundary (e.g. by existing a `with hanzoanalytics.new_context():` block already). If the passed exception was raised and caught, the captured stack trace will consist of every frame between where the exception was raised and the point at which it is captured (the "traceback"). If the passed exception was never raised, e.g. if you call `hanzoanalytics.capture_exception(ValueError("Some Error"))`, the stack trace captured will be the full stack trace at the moment the exception was captured. Note that heavy use of contexts will lead to truncated stack traces, as the exception will be captured by the context entered most recently, which may not be the point you catch the exception for the final time in your code. It's recommended to use contexts sparingly, for this reason. `capture_exception` takes the same set of optional arguments as `capture`.
Capture exception is idempotent - if it is called twice with the same exception instance, only a occurrence will be tracked in hanzo_insights. This is because, generally, contexts will cause exceptions to be captured automatically. However, to ensure you track an exception, if you catch and do not re-raise it, capturing it manually is recommended, unless you are certain it will have crossed a context boundary (e.g. by existing a `with hanzo_insights.new_context():` block already). If the passed exception was raised and caught, the captured stack trace will consist of every frame between where the exception was raised and the point at which it is captured (the "traceback"). If the passed exception was never raised, e.g. if you call `hanzo_insights.capture_exception(ValueError("Some Error"))`, the stack trace captured will be the full stack trace at the moment the exception was captured. Note that heavy use of contexts will lead to truncated stack traces, as the exception will be captured by the context entered most recently, which may not be the point you catch the exception for the final time in your code. It's recommended to use contexts sparingly, for this reason. `capture_exception` takes the same set of optional arguments as `capture`.
Examples:
```python
@@ -523,7 +523,7 @@ def feature_enabled(
disable_geoip: Whether to disable GeoIP lookup
Details:
You can call `hanzoanalytics.load_feature_flags()` before to make sure you're not doing unexpected requests.
You can call `hanzo_insights.load_feature_flags()` before to make sure you're not doing unexpected requests.
Examples:
```python
@@ -670,7 +670,7 @@ def get_feature_flag_result(
Example:
```python
result = hanzoanalytics.get_feature_flag_result('beta-feature', 'distinct_id')
result = hanzo_insights.get_feature_flag_result('beta-feature', 'distinct_id')
if result and result.enabled:
# Use the variant and payload
print(f"Variant: {result.variant}")
+3
View File
@@ -0,0 +1,3 @@
from hanzo_insights.ai.prompts import Prompts
__all__ = ["Prompts"]
@@ -10,20 +10,20 @@ import time
import uuid
from typing import Any, Dict, List, Optional
from hanzoanalytics.ai.types import StreamingContentBlock, TokenUsage, ToolInProgress
from hanzoanalytics.ai.utils import (
from hanzo_insights.ai.types import StreamingContentBlock, TokenUsage, ToolInProgress
from hanzo_insights.ai.utils import (
call_llm_and_track_usage,
merge_usage_stats,
)
from hanzoanalytics.ai.anthropic.anthropic_converter import (
from hanzo_insights.ai.anthropic.anthropic_converter import (
extract_anthropic_usage_from_event,
handle_anthropic_content_block_start,
handle_anthropic_text_delta,
handle_anthropic_tool_delta,
finalize_anthropic_tool_input,
)
from hanzoanalytics.ai.sanitization import sanitize_anthropic
from hanzoanalytics.client import Client as PostHogClient
from hanzo_insights.ai.sanitization import sanitize_anthropic
from hanzo_insights.client import Client as PostHogClient
from posthog import setup
@@ -215,12 +215,12 @@ class WrappedMessages(Messages):
content_blocks: List[StreamingContentBlock],
accumulated_content: str,
):
from hanzoanalytics.ai.types import StreamingEventData
from hanzoanalytics.ai.anthropic.anthropic_converter import (
from hanzo_insights.ai.types import StreamingEventData
from hanzo_insights.ai.anthropic.anthropic_converter import (
format_anthropic_streaming_input,
format_anthropic_streaming_output_complete,
)
from hanzoanalytics.ai.utils import capture_streaming_event
from hanzo_insights.ai.utils import capture_streaming_event
# Prepare standardized event data
formatted_input = format_anthropic_streaming_input(kwargs)
@@ -11,20 +11,20 @@ import uuid
from typing import Any, Dict, List, Optional
from posthog import setup
from hanzoanalytics.ai.types import StreamingContentBlock, TokenUsage, ToolInProgress
from hanzoanalytics.ai.utils import (
from hanzo_insights.ai.types import StreamingContentBlock, TokenUsage, ToolInProgress
from hanzo_insights.ai.utils import (
call_llm_and_track_usage_async,
merge_usage_stats,
)
from hanzoanalytics.ai.anthropic.anthropic_converter import (
from hanzo_insights.ai.anthropic.anthropic_converter import (
extract_anthropic_usage_from_event,
handle_anthropic_content_block_start,
handle_anthropic_text_delta,
handle_anthropic_tool_delta,
finalize_anthropic_tool_input,
)
from hanzoanalytics.ai.sanitization import sanitize_anthropic
from hanzoanalytics.client import Client as PostHogClient
from hanzo_insights.ai.sanitization import sanitize_anthropic
from hanzo_insights.client import Client as PostHogClient
class AsyncAnthropic(anthropic.AsyncAnthropic):
@@ -215,12 +215,12 @@ class AsyncWrappedMessages(AsyncMessages):
content_blocks: List[StreamingContentBlock],
accumulated_content: str,
):
from hanzoanalytics.ai.types import StreamingEventData
from hanzoanalytics.ai.anthropic.anthropic_converter import (
from hanzo_insights.ai.types import StreamingEventData
from hanzo_insights.ai.anthropic.anthropic_converter import (
format_anthropic_streaming_input,
format_anthropic_streaming_output_complete,
)
from hanzoanalytics.ai.utils import capture_streaming_event
from hanzo_insights.ai.utils import capture_streaming_event
# Prepare standardized event data
formatted_input = format_anthropic_streaming_input(kwargs)
@@ -8,7 +8,7 @@ into standardized formats for PostHog tracking.
import json
from typing import Any, Dict, List, Optional, Tuple
from hanzoanalytics.ai.types import (
from hanzo_insights.ai.types import (
FormattedContentItem,
FormattedFunctionCall,
FormattedMessage,
@@ -17,7 +17,7 @@ from hanzoanalytics.ai.types import (
TokenUsage,
ToolInProgress,
)
from hanzoanalytics.ai.utils import serialize_raw_usage
from hanzo_insights.ai.utils import serialize_raw_usage
def format_anthropic_response(response: Any) -> List[FormattedMessage]:
@@ -427,7 +427,7 @@ def format_anthropic_streaming_input(kwargs: Dict[str, Any]) -> Any:
Returns:
Formatted input ready for PostHog tracking
"""
from hanzoanalytics.ai.utils import merge_system_prompt
from hanzo_insights.ai.utils import merge_system_prompt
return merge_system_prompt(kwargs, "anthropic")
@@ -7,9 +7,9 @@ except ImportError:
from typing import Optional
from hanzoanalytics.ai.anthropic.anthropic import WrappedMessages
from hanzoanalytics.ai.anthropic.anthropic_async import AsyncWrappedMessages
from hanzoanalytics.client import Client as PostHogClient
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
@@ -3,8 +3,8 @@ import time
import uuid
from typing import Any, Dict, Optional
from hanzoanalytics.ai.types import TokenUsage, StreamingEventData
from hanzoanalytics.ai.utils import merge_system_prompt
from hanzo_insights.ai.types import TokenUsage, StreamingEventData
from hanzo_insights.ai.utils import merge_system_prompt
try:
from google import genai
@@ -14,18 +14,18 @@ except ImportError:
)
from posthog import setup
from hanzoanalytics.ai.utils import (
from hanzo_insights.ai.utils import (
call_llm_and_track_usage,
capture_streaming_event,
merge_usage_stats,
)
from hanzoanalytics.ai.gemini.gemini_converter import (
from hanzo_insights.ai.gemini.gemini_converter import (
extract_gemini_usage_from_chunk,
extract_gemini_content_from_chunk,
format_gemini_streaming_output,
)
from hanzoanalytics.ai.sanitization import sanitize_gemini
from hanzoanalytics.client import Client as PostHogClient
from hanzo_insights.ai.sanitization import sanitize_gemini
from hanzo_insights.client import Client as PostHogClient
class Client:
@@ -3,8 +3,8 @@ import time
import uuid
from typing import Any, Dict, Optional
from hanzoanalytics.ai.types import TokenUsage, StreamingEventData
from hanzoanalytics.ai.utils import merge_system_prompt
from hanzo_insights.ai.types import TokenUsage, StreamingEventData
from hanzo_insights.ai.utils import merge_system_prompt
try:
from google import genai
@@ -14,18 +14,18 @@ except ImportError:
)
from posthog import setup
from hanzoanalytics.ai.utils import (
from hanzo_insights.ai.utils import (
call_llm_and_track_usage_async,
capture_streaming_event,
merge_usage_stats,
)
from hanzoanalytics.ai.gemini.gemini_converter import (
from hanzo_insights.ai.gemini.gemini_converter import (
extract_gemini_usage_from_chunk,
extract_gemini_content_from_chunk,
format_gemini_streaming_output,
)
from hanzoanalytics.ai.sanitization import sanitize_gemini
from hanzoanalytics.client import Client as PostHogClient
from hanzo_insights.ai.sanitization import sanitize_gemini
from hanzo_insights.client import Client as PostHogClient
class AsyncClient:
@@ -7,12 +7,12 @@ into standardized formats for PostHog tracking.
from typing import Any, Dict, List, Optional, TypedDict, Union
from hanzoanalytics.ai.types import (
from hanzo_insights.ai.types import (
FormattedContentItem,
FormattedMessage,
TokenUsage,
)
from hanzoanalytics.ai.utils import serialize_raw_usage
from hanzo_insights.ai.utils import serialize_raw_usage
class GeminiPart(TypedDict, total=False):
@@ -349,7 +349,7 @@ def format_gemini_input_with_system(
if system_instruction is not None:
has_system = any(msg.get("role") == "system" for msg in formatted_messages)
if not has_system:
from hanzoanalytics.ai.types import FormattedMessage
from hanzo_insights.ai.types import FormattedMessage
system_message: FormattedMessage = {
"role": "system",
@@ -42,11 +42,11 @@ from langchain_core.outputs import ChatGeneration, LLMResult
from pydantic import BaseModel
from posthog import setup
from hanzoanalytics.ai.sanitization import sanitize_langchain
from hanzoanalytics.ai.utils import get_model_params, with_privacy_mode
from hanzoanalytics.client import Client
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
log = logging.getLogger("hanzoanalytics")
log = logging.getLogger("hanzo_insights")
@dataclass
@@ -2,7 +2,7 @@ import time
import uuid
from typing import Any, Dict, List, Optional
from hanzoanalytics.ai.types import TokenUsage
from hanzo_insights.ai.types import TokenUsage
try:
import openai
@@ -11,20 +11,20 @@ except ImportError:
"Please install the OpenAI SDK to use this feature: 'pip install openai'"
)
from hanzoanalytics.ai.utils import (
from hanzo_insights.ai.utils import (
call_llm_and_track_usage,
extract_available_tool_calls,
merge_usage_stats,
with_privacy_mode,
)
from hanzoanalytics.ai.openai.openai_converter import (
from hanzo_insights.ai.openai.openai_converter import (
extract_openai_usage_from_chunk,
extract_openai_content_from_chunk,
extract_openai_tool_calls_from_chunk,
accumulate_openai_tool_calls,
)
from hanzoanalytics.ai.sanitization import sanitize_openai, sanitize_openai_response
from hanzoanalytics.client import Client as PostHogClient
from hanzo_insights.ai.sanitization import sanitize_openai, sanitize_openai_response
from hanzo_insights.client import Client as PostHogClient
from posthog import setup
@@ -189,12 +189,12 @@ class WrappedResponses:
available_tool_calls: Optional[List[Dict[str, Any]]] = None,
model_from_response: Optional[str] = None,
):
from hanzoanalytics.ai.types import StreamingEventData
from hanzoanalytics.ai.openai.openai_converter import (
from hanzo_insights.ai.types import StreamingEventData
from hanzo_insights.ai.openai.openai_converter import (
format_openai_streaming_input,
format_openai_streaming_output,
)
from hanzoanalytics.ai.utils import capture_streaming_event
from hanzo_insights.ai.utils import capture_streaming_event
# Prepare standardized event data
formatted_input = format_openai_streaming_input(kwargs, "responses")
@@ -416,12 +416,12 @@ class WrappedCompletions:
available_tool_calls: Optional[List[Dict[str, Any]]] = None,
model_from_response: Optional[str] = None,
):
from hanzoanalytics.ai.types import StreamingEventData
from hanzoanalytics.ai.openai.openai_converter import (
from hanzo_insights.ai.types import StreamingEventData
from hanzo_insights.ai.openai.openai_converter import (
format_openai_streaming_input,
format_openai_streaming_output,
)
from hanzoanalytics.ai.utils import capture_streaming_event
from hanzo_insights.ai.utils import capture_streaming_event
# Prepare standardized event data
formatted_input = format_openai_streaming_input(kwargs, "chat")
@@ -2,7 +2,7 @@ import time
import uuid
from typing import Any, Dict, List, Optional
from hanzoanalytics.ai.types import TokenUsage
from hanzo_insights.ai.types import TokenUsage
try:
import openai
@@ -12,22 +12,22 @@ except ImportError:
)
from posthog import setup
from hanzoanalytics.ai.utils import (
from hanzo_insights.ai.utils import (
call_llm_and_track_usage_async,
extract_available_tool_calls,
get_model_params,
merge_usage_stats,
with_privacy_mode,
)
from hanzoanalytics.ai.openai.openai_converter import (
from hanzo_insights.ai.openai.openai_converter import (
extract_openai_usage_from_chunk,
extract_openai_content_from_chunk,
extract_openai_tool_calls_from_chunk,
accumulate_openai_tool_calls,
format_openai_streaming_output,
)
from hanzoanalytics.ai.sanitization import sanitize_openai, sanitize_openai_response
from hanzoanalytics.client import Client as PostHogClient
from hanzo_insights.ai.sanitization import sanitize_openai, sanitize_openai_response
from hanzo_insights.client import Client as PostHogClient
class AsyncOpenAI(openai.AsyncOpenAI):
@@ -42,7 +42,7 @@ class AsyncOpenAI(openai.AsyncOpenAI):
Args:
api_key: OpenAI API key.
posthog_client: If provided, events will be captured via this client instead
of the global hanzoanalytics.
of the global hanzo_insights.
**openai_config: Any additional keyword args to set on openai (e.g. organization="xxx").
"""
@@ -8,7 +8,7 @@ Chat Completions API and Responses API formats.
from typing import Any, Dict, List, Optional
from hanzoanalytics.ai.types import (
from hanzo_insights.ai.types import (
FormattedContentItem,
FormattedFunctionCall,
FormattedImageContent,
@@ -16,7 +16,7 @@ from hanzoanalytics.ai.types import (
FormattedTextContent,
TokenUsage,
)
from hanzoanalytics.ai.utils import serialize_raw_usage
from hanzo_insights.ai.utils import serialize_raw_usage
def format_openai_response(response: Any) -> List[FormattedMessage]:
@@ -755,6 +755,6 @@ def format_openai_streaming_input(
Returns:
Formatted input ready for PostHog tracking
"""
from hanzoanalytics.ai.utils import merge_system_prompt
from hanzo_insights.ai.utils import merge_system_prompt
return merge_system_prompt(kwargs, "openai")
@@ -5,19 +5,19 @@ except ImportError:
"Please install the Open AI SDK to use this feature: 'pip install openai'"
)
from hanzoanalytics.ai.openai.openai import (
from hanzo_insights.ai.openai.openai import (
WrappedBeta,
WrappedChat,
WrappedEmbeddings,
WrappedResponses,
)
from hanzoanalytics.ai.openai.openai_async import WrappedBeta as AsyncWrappedBeta
from hanzoanalytics.ai.openai.openai_async import WrappedChat as AsyncWrappedChat
from hanzoanalytics.ai.openai.openai_async import WrappedEmbeddings as AsyncWrappedEmbeddings
from hanzoanalytics.ai.openai.openai_async import WrappedResponses as AsyncWrappedResponses
from hanzo_insights.ai.openai.openai_async import WrappedBeta as AsyncWrappedBeta
from hanzo_insights.ai.openai.openai_async import WrappedChat as AsyncWrappedChat
from hanzo_insights.ai.openai.openai_async import WrappedEmbeddings as AsyncWrappedEmbeddings
from hanzo_insights.ai.openai.openai_async import WrappedResponses as AsyncWrappedResponses
from typing import Optional
from hanzoanalytics.client import Client as PostHogClient
from hanzo_insights.client import Client as PostHogClient
from posthog import setup
@@ -33,7 +33,7 @@ class AzureOpenAI(openai.AzureOpenAI):
Args:
api_key: Azure OpenAI API key.
posthog_client: If provided, events will be captured via this client instead
of the global hanzoanalytics.
of the global hanzo_insights.
**openai_config: Any additional keyword args to set on Azure OpenAI (e.g. azure_endpoint="xxx").
"""
super().__init__(**kwargs)
@@ -71,7 +71,7 @@ class AsyncAzureOpenAI(openai.AsyncAzureOpenAI):
Args:
api_key: Azure OpenAI API key.
posthog_client: If provided, events will be captured via this client instead
of the global hanzoanalytics.
of the global hanzo_insights.
**openai_config: Any additional keyword args to set on Azure OpenAI (e.g. azure_endpoint="xxx").
"""
super().__init__(**kwargs)
@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Union
if TYPE_CHECKING:
from agents.tracing import Trace
from hanzoanalytics.client import Client
from hanzo_insights.client import Client
try:
import agents # noqa: F401
@@ -14,7 +14,7 @@ except ImportError:
"Please install the OpenAI Agents SDK to use this feature: 'pip install openai-agents'"
)
from hanzoanalytics.ai.openai_agents.processor import PostHogTracingProcessor
from hanzo_insights.ai.openai_agents.processor import PostHogTracingProcessor
__all__ = ["PostHogTracingProcessor", "instrument"]
@@ -45,7 +45,7 @@ def instrument(
Example:
```python
from hanzoanalytics.ai.openai_agents import instrument
from hanzo_insights.ai.openai_agents import instrument
# Simple setup
instrument(distinct_id="user@example.com")
@@ -21,9 +21,9 @@ from agents.tracing.span_data import (
)
from posthog import setup
from hanzoanalytics.client import Client
from hanzo_insights.client import Client
log = logging.getLogger("hanzoanalytics")
log = logging.getLogger("hanzo_insights")
def _ensure_serializable(obj: Any) -> Any:
@@ -64,7 +64,7 @@ class PostHogTracingProcessor(TracingProcessor):
```python
from agents import Agent, Runner
from agents.tracing import add_trace_processor
from hanzoanalytics.ai.openai_agents import PostHogTracingProcessor
from hanzo_insights.ai.openai_agents import PostHogTracingProcessor
# Create and register the processor
processor = PostHogTracingProcessor(
@@ -10,12 +10,12 @@ import time
import urllib.parse
from typing import Any, Dict, Optional, Union
from hanzoanalytics.request import USER_AGENT, _get_session
from hanzoanalytics.utils import remove_trailing_slash
from hanzo_insights.request import USER_AGENT, _get_session
from hanzo_insights.utils import remove_trailing_slash
log = logging.getLogger("hanzoanalytics")
log = logging.getLogger("hanzo_insights")
APP_ENDPOINT = "https://us.hanzoanalytics.com"
APP_ENDPOINT = "https://us.hanzo_insights.com"
DEFAULT_CACHE_TTL_SECONDS = 300 # 5 minutes
PromptVariables = Dict[str, Union[str, int, float, bool]]
@@ -61,17 +61,17 @@ class Prompts:
Examples:
```python
from posthog import Posthog
from hanzoanalytics.ai.prompts import Prompts
from hanzo_insights.ai.prompts import Prompts
# With PostHog client
posthog = Posthog('phc_xxx', host='https://us.hanzoanalytics.com', personal_api_key='phx_xxx')
posthog = Posthog('phc_xxx', host='https://us.hanzo_insights.com', personal_api_key='phx_xxx')
prompts = Prompts(posthog)
# Or with direct options (no PostHog client needed)
prompts = Prompts(
personal_api_key='phx_xxx',
project_api_key='phc_xxx',
host='https://us.hanzoanalytics.com',
host='https://us.hanzo_insights.com',
)
# Fetch with caching and fallback
@@ -3,14 +3,14 @@ import uuid
from typing import Any, Callable, Dict, List, Optional, cast
from posthog import get_tags, identify_context, new_context, tag, contexts
from hanzoanalytics.ai.sanitization import (
from hanzo_insights.ai.sanitization import (
sanitize_anthropic,
sanitize_gemini,
sanitize_langchain,
sanitize_openai,
)
from hanzoanalytics.ai.types import FormattedMessage, StreamingEventData, TokenUsage
from hanzoanalytics.client import Client as PostHogClient
from hanzo_insights.ai.types import FormattedMessage, StreamingEventData, TokenUsage
from hanzo_insights.client import Client as PostHogClient
_TOKEN_PROPERTY_KEYS = frozenset(
@@ -193,19 +193,19 @@ def get_usage(response, provider: str) -> TokenUsage:
Delegates to provider-specific converter functions.
"""
if provider == "anthropic":
from hanzoanalytics.ai.anthropic.anthropic_converter import (
from hanzo_insights.ai.anthropic.anthropic_converter import (
extract_anthropic_usage_from_response,
)
return extract_anthropic_usage_from_response(response)
elif provider == "openai":
from hanzoanalytics.ai.openai.openai_converter import (
from hanzo_insights.ai.openai.openai_converter import (
extract_openai_usage_from_response,
)
return extract_openai_usage_from_response(response)
elif provider == "gemini":
from hanzoanalytics.ai.gemini.gemini_converter import (
from hanzo_insights.ai.gemini.gemini_converter import (
extract_gemini_usage_from_response,
)
@@ -219,15 +219,15 @@ def format_response(response, provider: str):
Format a regular (non-streaming) response.
"""
if provider == "anthropic":
from hanzoanalytics.ai.anthropic.anthropic_converter import format_anthropic_response
from hanzo_insights.ai.anthropic.anthropic_converter import format_anthropic_response
return format_anthropic_response(response)
elif provider == "openai":
from hanzoanalytics.ai.openai.openai_converter import format_openai_response
from hanzo_insights.ai.openai.openai_converter import format_openai_response
return format_openai_response(response)
elif provider == "gemini":
from hanzoanalytics.ai.gemini.gemini_converter import format_gemini_response
from hanzo_insights.ai.gemini.gemini_converter import format_gemini_response
return format_gemini_response(response)
return []
@@ -238,15 +238,15 @@ def extract_available_tool_calls(provider: str, kwargs: Dict[str, Any]):
Extract available tool calls for the given provider.
"""
if provider == "anthropic":
from hanzoanalytics.ai.anthropic.anthropic_converter import extract_anthropic_tools
from hanzo_insights.ai.anthropic.anthropic_converter import extract_anthropic_tools
return extract_anthropic_tools(kwargs)
elif provider == "gemini":
from hanzoanalytics.ai.gemini.gemini_converter import extract_gemini_tools
from hanzo_insights.ai.gemini.gemini_converter import extract_gemini_tools
return extract_gemini_tools(kwargs)
elif provider == "openai":
from hanzoanalytics.ai.openai.openai_converter import extract_openai_tools
from hanzo_insights.ai.openai.openai_converter import extract_openai_tools
return extract_openai_tools(kwargs)
return None
@@ -259,19 +259,19 @@ def merge_system_prompt(
Merge system prompts and format messages for the given provider.
"""
if provider == "anthropic":
from hanzoanalytics.ai.anthropic.anthropic_converter import format_anthropic_input
from hanzo_insights.ai.anthropic.anthropic_converter import format_anthropic_input
messages = kwargs.get("messages") or []
system = kwargs.get("system")
return format_anthropic_input(messages, system)
elif provider == "gemini":
from hanzoanalytics.ai.gemini.gemini_converter import format_gemini_input_with_system
from hanzo_insights.ai.gemini.gemini_converter import format_gemini_input_with_system
contents = kwargs.get("contents", [])
config = kwargs.get("config")
return format_gemini_input_with_system(contents, config)
elif provider == "openai":
from hanzoanalytics.ai.openai.openai_converter import format_openai_input
from hanzo_insights.ai.openai.openai_converter import format_openai_input
# For OpenAI, handle both Chat Completions and Responses API
messages_param = kwargs.get("messages")
@@ -5,7 +5,7 @@ from datetime import datetime
import numbers
from uuid import UUID
from hanzoanalytics.types import SendFeatureFlagsOptions
from hanzo_insights.types import SendFeatureFlagsOptions
ID_TYPES = Union[numbers.Number, str, UUID, int]
@@ -11,9 +11,9 @@ from dateutil.tz import tzutc
from six import string_types
from typing_extensions import Unpack
from hanzoanalytics.args import ID_TYPES, ExceptionArg, OptionalCaptureArgs, OptionalSetArgs
from hanzoanalytics.consumer import Consumer
from hanzoanalytics.contexts import (
from hanzo_insights.args import ID_TYPES, ExceptionArg, OptionalCaptureArgs, OptionalSetArgs
from hanzo_insights.consumer import Consumer
from hanzo_insights.contexts import (
_get_current_context,
get_capture_exception_code_variables_context,
get_code_variables_ignore_patterns_context,
@@ -23,8 +23,8 @@ from hanzoanalytics.contexts import (
get_context_session_id,
new_context,
)
from hanzoanalytics.exception_capture import ExceptionCapture
from hanzoanalytics.exception_utils import (
from hanzo_insights.exception_capture import ExceptionCapture
from hanzo_insights.exception_utils import (
DEFAULT_CODE_VARIABLES_IGNORE_PATTERNS,
DEFAULT_CODE_VARIABLES_MASK_PATTERNS,
exc_info_from_error,
@@ -34,18 +34,18 @@ from hanzoanalytics.exception_utils import (
mark_exception_as_captured,
try_attach_code_variables_to_frames,
)
from hanzoanalytics.feature_flags import (
from hanzo_insights.feature_flags import (
InconclusiveMatchError,
RequiresServerEvaluation,
match_feature_flag_properties,
resolve_bucketing_value,
)
from hanzoanalytics.flag_definition_cache import (
from hanzo_insights.flag_definition_cache import (
FlagDefinitionCacheData,
FlagDefinitionCacheProvider,
)
from hanzoanalytics.poller import Poller
from hanzoanalytics.request import (
from hanzo_insights.poller import Poller
from hanzo_insights.request import (
DEFAULT_HOST,
APIError,
QuotaLimitError,
@@ -57,7 +57,7 @@ from hanzoanalytics.request import (
get,
remote_config,
)
from hanzoanalytics.types import (
from hanzo_insights.types import (
FeatureFlag,
FeatureFlagError,
FeatureFlagResult,
@@ -71,7 +71,7 @@ from hanzoanalytics.types import (
to_payloads,
to_values,
)
from hanzoanalytics.utils import (
from hanzo_insights.utils import (
FlagCache,
RedisFlagCache,
SizeLimitedDict,
@@ -79,7 +79,7 @@ from hanzoanalytics.utils import (
guess_timezone,
system_context,
)
from hanzoanalytics.version import VERSION
from hanzo_insights.version import VERSION
try:
import queue
@@ -158,13 +158,13 @@ class Client(object):
```python
from posthog import Posthog
posthog = Posthog('<ph_project_api_key>', host='<ph_client_api_host>')
hanzoanalytics.debug = True
hanzo_insights.debug = True
if settings.TEST:
hanzoanalytics.disabled = True
hanzo_insights.disabled = True
```
"""
log = logging.getLogger("hanzoanalytics")
log = logging.getLogger("hanzo_insights")
def __init__(
self,
@@ -342,9 +342,9 @@ class Client(object):
Examples:
```python
with hanzoanalytics.new_context():
with hanzo_insights.new_context():
identify_context('<distinct_id>')
hanzoanalytics.capture('event_name')
hanzo_insights.capture('event_name')
```
Category:
@@ -438,7 +438,7 @@ class Client(object):
Examples:
```python
payloads = hanzoanalytics.get_feature_payloads('<distinct_id>')
payloads = hanzo_insights.get_feature_payloads('<distinct_id>')
```
Category:
@@ -480,7 +480,7 @@ class Client(object):
Examples:
```python
result = hanzoanalytics.get_feature_flags_and_payloads('<distinct_id>')
result = hanzo_insights.get_feature_flags_and_payloads('<distinct_id>')
```
Category:
@@ -522,7 +522,7 @@ class Client(object):
Examples:
```python
decision = hanzoanalytics.get_flags_decision('user123')
decision = hanzo_insights.get_flags_decision('user123')
```
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://hanzoanalytics.com/docs/product-analytics/capture-events)
Captures an event manually. [Learn about capture best practices](https://hanzo_insights.com/docs/product-analytics/capture-events)
Args:
event: The event name to capture.
@@ -585,20 +585,20 @@ class Client(object):
Examples:
```python
# Anonymous event
hanzoanalytics.capture('some-anon-event')
hanzo_insights.capture('some-anon-event')
```
```python
# Context usage
from posthog import identify_context, new_context
with new_context():
identify_context('distinct_id_of_the_user')
hanzoanalytics.capture('user_signed_up')
hanzoanalytics.capture('user_logged_in')
hanzoanalytics.capture('some-custom-action', distinct_id='distinct_id_of_the_user')
hanzo_insights.capture('user_signed_up')
hanzo_insights.capture('user_logged_in')
hanzo_insights.capture('some-custom-action', distinct_id='distinct_id_of_the_user')
```
```python
# Set event properties
hanzoanalytics.capture(
hanzo_insights.capture(
"user_signed_up",
distinct_id="distinct_id_of_the_user",
properties={
@@ -609,7 +609,7 @@ class Client(object):
```
```python
# Page view event
hanzoanalytics.capture('$pageview', distinct_id="distinct_id_of_the_user", properties={'$current_url': 'https://example.com'})
hanzo_insights.capture('$pageview', distinct_id="distinct_id_of_the_user", properties={'$current_url': 'https://example.com'})
```
Category:
@@ -769,7 +769,7 @@ class Client(object):
Examples:
```python
# Set with distinct id
hanzoanalytics.set(distinct_id='user123', properties={'name': 'Max Hedgehog'})
hanzo_insights.set(distinct_id='user123', properties={'name': 'Max Hedgehog'})
```
Category:
@@ -816,7 +816,7 @@ class Client(object):
Examples:
```python
hanzoanalytics.set_once(distinct_id='user123', properties={'initial_signup_date': '2024-01-01'})
hanzo_insights.set_once(distinct_id='user123', properties={'initial_signup_date': '2024-01-01'})
```
Category:
@@ -873,7 +873,7 @@ class Client(object):
Examples:
```python
hanzoanalytics.group_identify('company', 'company_id_in_your_db', {
hanzo_insights.group_identify('company', 'company_id_in_your_db', {
'name': 'Awesome Inc.',
'employees': 11
})
@@ -928,7 +928,7 @@ class Client(object):
Examples:
```python
hanzoanalytics.alias(previous_id='distinct_id', distinct_id='alias_id')
hanzo_insights.alias(previous_id='distinct_id', distinct_id='alias_id')
```
Category:
@@ -978,7 +978,7 @@ class Client(object):
# Some code that might fail
pass
except Exception as e:
hanzoanalytics.capture_exception(e, 'user_distinct_id', properties=additional_properties)
hanzo_insights.capture_exception(e, 'user_distinct_id', properties=additional_properties)
```
Category:
@@ -1168,8 +1168,8 @@ class Client(object):
Examples:
```python
hanzoanalytics.capture('event_name')
hanzoanalytics.flush() # Ensures the event is sent immediately
hanzo_insights.capture('event_name')
hanzo_insights.flush() # Ensures the event is sent immediately
```
"""
queue = self.queue
@@ -1184,7 +1184,7 @@ class Client(object):
Examples:
```python
hanzoanalytics.join()
hanzo_insights.join()
```
"""
if self.consumers:
@@ -1212,7 +1212,7 @@ class Client(object):
Examples:
```python
hanzoanalytics.shutdown()
hanzo_insights.shutdown()
```
"""
self.flush()
@@ -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://hanzoanalytics.com/docs/api/overview"
"[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"
)
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://hanzoanalytics.com/docs/api/overview",
"More information: https://hanzo_insights.com/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://hanzoanalytics.com/docs/billing/limits-alerts"
"[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"
)
# Reset all feature flag data when quota limited
self.feature_flags = []
@@ -1385,7 +1385,7 @@ class Client(object):
Examples:
```python
hanzoanalytics.load_feature_flags()
hanzo_insights.load_feature_flags()
```
Category:
@@ -1520,11 +1520,11 @@ class Client(object):
Examples:
```python
is_my_flag_enabled = hanzoanalytics.feature_enabled('flag-key', 'distinct_id_of_your_user')
is_my_flag_enabled = hanzo_insights.feature_enabled('flag-key', 'distinct_id_of_your_user')
if is_my_flag_enabled:
# Do something differently for this user
# Optional: fetch the payload
matched_flag_payload = hanzoanalytics.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')
matched_flag_payload = hanzo_insights.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')
```
Category:
@@ -1718,7 +1718,7 @@ class Client(object):
Examples:
```python
flag_result = hanzoanalytics.get_feature_flag_result('flag-key', 'distinct_id_of_your_user')
flag_result = hanzo_insights.get_feature_flag_result('flag-key', 'distinct_id_of_your_user')
if flag_result and flag_result.get_value() == 'variant-key':
# Do something differently for this user
# Optional: fetch the payload
@@ -1780,11 +1780,11 @@ class Client(object):
Examples:
```python
enabled_variant = hanzoanalytics.get_feature_flag('flag-key', 'distinct_id_of_your_user')
enabled_variant = hanzo_insights.get_feature_flag('flag-key', 'distinct_id_of_your_user')
if enabled_variant == 'variant-key': # replace 'variant-key' with the key of your variant
# Do something differently for this user
# Optional: fetch the payload
matched_flag_payload = hanzoanalytics.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')
matched_flag_payload = hanzo_insights.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')
```
Category:
@@ -1874,12 +1874,12 @@ class Client(object):
Examples:
```python
is_my_flag_enabled = hanzoanalytics.feature_enabled('flag-key', 'distinct_id_of_your_user')
is_my_flag_enabled = hanzo_insights.feature_enabled('flag-key', 'distinct_id_of_your_user')
if is_my_flag_enabled:
# Do something differently for this user
# Optional: fetch the payload
matched_flag_payload = hanzoanalytics.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')
matched_flag_payload = hanzo_insights.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user')
```
Category:
@@ -2071,7 +2071,7 @@ class Client(object):
Examples:
```python
hanzoanalytics.get_all_flags('distinct_id_of_your_user')
hanzo_insights.get_all_flags('distinct_id_of_your_user')
```
Category:
@@ -2118,7 +2118,7 @@ class Client(object):
Examples:
```python
hanzoanalytics.get_all_flags_and_payloads('distinct_id_of_your_user')
hanzo_insights.get_all_flags_and_payloads('distinct_id_of_your_user')
```
Category:
@@ -3,7 +3,7 @@ import logging
import time
from threading import Thread
from hanzoanalytics.request import APIError, DatetimeSerializer, batch_post
from hanzo_insights.request import APIError, DatetimeSerializer, batch_post
try:
from queue import Empty
@@ -21,7 +21,7 @@ BATCH_SIZE_LIMIT = 5 * 1024 * 1024
class Consumer(Thread):
"""Consumes the messages from the client's queue."""
log = logging.getLogger("hanzoanalytics")
log = logging.getLogger("hanzo_insights")
def __init__(
self,
@@ -4,7 +4,7 @@ from typing import Optional, Any, Callable, Dict, TypeVar, cast, TYPE_CHECKING
if TYPE_CHECKING:
# To avoid circular imports
from hanzoanalytics.client import Client
from hanzo_insights.client import Client
class ContextScope:
@@ -134,25 +134,25 @@ def new_context(
If provided, the client will be used to capture exceptions within the context.
If not provided, the default (global) client will be used. Note that the passed
client is only used to capture exceptions within the context - other events captured
within the context via `Client.capture` or `hanzoanalytics.capture` will still carry the context
within the context via `Client.capture` or `hanzo_insights.capture` will still carry the context
state (tags, identity, session id), but will be captured by the client directly used (or
the global one, in the case of `hanzoanalytics.capture`)
the global one, in the case of `hanzo_insights.capture`)
Examples:
```python
# Inherit parent context tags
with hanzoanalytics.new_context():
hanzoanalytics.tag("request_id", "123")
with hanzo_insights.new_context():
hanzo_insights.tag("request_id", "123")
# Both this event and the exception will be tagged with the context tags
hanzoanalytics.capture("event_name", {"property": "value"})
hanzo_insights.capture("event_name", {"property": "value"})
raise ValueError("Something went wrong")
```
```python
# Start with fresh context (no inherited tags)
with hanzoanalytics.new_context(fresh=True):
hanzoanalytics.tag("request_id", "123")
with hanzo_insights.new_context(fresh=True):
hanzo_insights.tag("request_id", "123")
# Both this event and the exception will be tagged with the context tags
hanzoanalytics.capture("event_name", {"property": "value"})
hanzo_insights.capture("event_name", {"property": "value"})
raise ValueError("Something went wrong")
```
@@ -189,7 +189,7 @@ def tag(key: str, value: Any) -> None:
Example:
```python
hanzoanalytics.tag("user_id", "123")
hanzo_insights.tag("user_id", "123")
```
Category:
@@ -221,7 +221,7 @@ def identify_context(distinct_id: str) -> None:
"""
Identify the current context with a distinct ID, associating all events captured in this or
child contexts with the given distinct ID (unless identify_context is called again). This is overridden by
distinct id's passed directly to hanzoanalytics.capture and related methods (identify, set etc). Entering a
distinct id's passed directly to hanzo_insights.capture and related methods (identify, set etc). Entering a
fresh context will clear the context-level distinct ID. The distinct-id passed should be uniquely associated
with one of your users. Events captured outside of a context, or in a context with no associated distinct
ID, will be assigned a random UUID, and captured as "personless".
@@ -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://hanzoanalytics.com/docs/data/sessions
session_id: The session ID to associate with the current context and its children. See https://hanzo_insights.com/docs/data/sessions
Category:
Contexts
@@ -373,20 +373,20 @@ F = TypeVar("F", bound=Callable[..., Any])
def scoped(fresh: bool = False, capture_exceptions: bool = True):
"""
Decorator that creates a new context for the function. Simply wraps
the function in a with hanzoanalytics.new_context(): block.
the function in a with hanzo_insights.new_context(): block.
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)
Example:
@hanzoanalytics.scoped()
@hanzo_insights.scoped()
def process_payment(payment_id):
hanzoanalytics.tag("payment_id", payment_id)
hanzoanalytics.tag("payment_method", "credit_card")
hanzo_insights.tag("payment_id", payment_id)
hanzo_insights.tag("payment_method", "credit_card")
# This event will be captured with tags
hanzoanalytics.capture("payment_started")
hanzo_insights.capture("payment_started")
# If this raises an exception, it will be captured with tags
# and then re-raised
some_risky_function()
@@ -9,13 +9,13 @@ import threading
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from hanzoanalytics.client import Client
from hanzo_insights.client import Client
class ExceptionCapture:
# TODO: Add client side rate limiting to prevent spamming the server with exceptions
log = logging.getLogger("hanzoanalytics")
log = logging.getLogger("hanzo_insights")
def __init__(self, client: "Client"):
self.client = client
@@ -30,7 +30,7 @@ from typing import ( # noqa: F401
cast,
)
from hanzoanalytics.args import ExceptionArg, ExcInfo # noqa: F401
from hanzo_insights.args import ExceptionArg, ExcInfo # noqa: F401
try:
# Python 3.11
@@ -9,12 +9,12 @@ from dateutil import parser
from dateutil.relativedelta import relativedelta
from posthog import utils
from hanzoanalytics.types import FlagValue
from hanzoanalytics.utils import convert_to_datetime_aware, is_valid_regex
from hanzo_insights.types import FlagValue
from hanzo_insights.utils import convert_to_datetime_aware, is_valid_regex
__LONG_SCALE__ = float(0xFFFFFFFFFFFFFFF)
log = logging.getLogger("hanzoanalytics")
log = logging.getLogger("hanzo_insights")
NONE_VALUES_ALLOWED_OPERATORS = ["is_not"]
@@ -10,7 +10,7 @@ functions) to share flag definitions and reduce API calls.
Usage:
from posthog import Posthog
from hanzoanalytics.flag_definition_cache import FlagDefinitionCacheProvider
from hanzo_insights.flag_definition_cache import FlagDefinitionCacheProvider
cache = RedisFlagDefinitionCache(redis_client, "my-team")
posthog = Posthog(
@@ -1,6 +1,6 @@
from typing import TYPE_CHECKING, cast
from posthog import contexts
from hanzoanalytics.client import Client
from hanzo_insights.client import Client
try:
from asgiref.sync import iscoroutinefunction, markcoroutinefunction
@@ -14,8 +14,8 @@ from requests.adapters import HTTPAdapter # type: ignore[import-untyped]
from urllib3.connection import HTTPConnection
from urllib3.util.retry import Retry
from hanzoanalytics.utils import remove_trailing_slash
from hanzoanalytics.version import VERSION
from hanzo_insights.utils import remove_trailing_slash
from hanzo_insights.version import VERSION
SocketOptions = List[Tuple[int, int, Union[int, bytes]]]
@@ -159,8 +159,8 @@ def disable_connection_reuse() -> None:
_pooling_enabled = False
US_INGESTION_ENDPOINT = "https://us.i.hanzoanalytics.com"
EU_INGESTION_ENDPOINT = "https://eu.i.hanzoanalytics.com"
US_INGESTION_ENDPOINT = "https://us.i.hanzo_insights.com"
EU_INGESTION_ENDPOINT = "https://eu.i.hanzo_insights.com"
DEFAULT_HOST = US_INGESTION_ENDPOINT
USER_AGENT = "posthog-python/" + VERSION
@@ -169,9 +169,9 @@ 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.hanzoanalytics.com", "https://us.hanzoanalytics.com"):
if trimmed_host in ("https://app.hanzo_insights.com", "https://us.hanzo_insights.com"):
return US_INGESTION_ENDPOINT
elif trimmed_host == "https://eu.hanzoanalytics.com":
elif trimmed_host == "https://eu.hanzo_insights.com":
return EU_INGESTION_ENDPOINT
else:
return host_or_default
@@ -187,7 +187,7 @@ def post(
**kwargs,
) -> requests.Response:
"""Post the `kwargs` to the API"""
log = logging.getLogger("hanzoanalytics")
log = logging.getLogger("hanzo_insights")
body = kwargs
body["sentAt"] = datetime.now(tz=tzutc()).isoformat()
url = remove_trailing_slash(host or DEFAULT_HOST) + path
@@ -217,7 +217,7 @@ def post(
def _process_response(
res: requests.Response, success_message: str, *, return_json: bool = True
) -> Union[requests.Response, Any]:
log = logging.getLogger("hanzoanalytics")
log = logging.getLogger("hanzo_insights")
if res.status_code == 200:
log.debug(success_message)
response = res.json() if return_json else res
@@ -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://hanzoanalytics.com/docs/billing/limits-alerts"
"[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"
)
raise QuotaLimitError(res.status_code, "Feature flags quota limited")
return response
@@ -341,7 +341,7 @@ def get(
- not_modified=True and data=None if server returns 304
- not_modified=False and data=response if server returns 200
"""
log = logging.getLogger("hanzoanalytics")
log = logging.getLogger("hanzo_insights")
full_url = remove_trailing_slash(host or DEFAULT_HOST) + url
headers = {"Authorization": "Bearer %s" % api_key, "User-Agent": USER_AGENT}
@@ -6,7 +6,7 @@ import unittest
def all_names():
for _, modname, _ in pkgutil.iter_modules(__path__):
yield "hanzoanalytics.test." + modname
yield "hanzo_insights.test." + modname
def all():
@@ -8,7 +8,7 @@ from posthog import identify_context, new_context
try:
from anthropic.types import Message, Usage
from hanzoanalytics.ai.anthropic import Anthropic, AsyncAnthropic
from hanzo_insights.ai.anthropic import Anthropic, AsyncAnthropic
ANTHROPIC_AVAILABLE = True
except ImportError:
@@ -105,7 +105,7 @@ class MockDelta:
@pytest.fixture
def mock_client():
with patch("hanzoanalytics.client.Client") as mock_client:
with patch("hanzo_insights.client.Client") as mock_client:
mock_client.privacy_mode = False
yield mock_client
@@ -6,7 +6,7 @@ import pytest
try:
from google import genai as google_genai
from hanzoanalytics.ai.gemini import Client
from hanzo_insights.ai.gemini import Client
GEMINI_AVAILABLE = True
except ImportError:
@@ -19,7 +19,7 @@ pytestmark = pytest.mark.skipif(
@pytest.fixture
def mock_client():
with patch("hanzoanalytics.client.Client") as mock_client:
with patch("hanzo_insights.client.Client") as mock_client:
mock_client.privacy_mode = False
yield mock_client
@@ -5,7 +5,7 @@ import pytest
try:
from google import genai as google_genai
from hanzoanalytics.ai.gemini import AsyncClient
from hanzo_insights.ai.gemini import AsyncClient
GEMINI_AVAILABLE = True
except ImportError:
@@ -21,7 +21,7 @@ pytestmark = [
@pytest.fixture
def mock_client():
with patch("hanzoanalytics.client.Client") as mock_client:
with patch("hanzo_insights.client.Client") as mock_client:
mock_client.privacy_mode = False
yield mock_client
@@ -21,8 +21,8 @@ try:
from langgraph.graph.state import END, START, StateGraph
from langgraph.prebuilt import create_react_agent
from hanzoanalytics.ai.langchain import CallbackHandler
from hanzoanalytics.ai.langchain.callbacks import GenerationMetadata, SpanMetadata
from hanzo_insights.ai.langchain import CallbackHandler
from hanzo_insights.ai.langchain.callbacks import GenerationMetadata, SpanMetadata
LANGCHAIN_AVAILABLE = True
except ImportError:
@@ -53,9 +53,9 @@ ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY")
@pytest.fixture(scope="function")
def mock_client():
with patch("hanzoanalytics.client.Client") as mock_client:
with patch("hanzo_insights.client.Client") as mock_client:
mock_client.privacy_mode = False
logging.getLogger("hanzoanalytics").setLevel(logging.DEBUG)
logging.getLogger("hanzo_insights").setLevel(logging.DEBUG)
yield mock_client
@@ -97,11 +97,11 @@ 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.hanzoanalytics.com"}},
{"kwargs": {"openai_api_base": "https://us.hanzo_insights.com"}},
run_id,
messages=[{"role": "user", "content": "Who won the world series in 2020?"}],
invocation_params={"temperature": 0.5},
metadata={"ls_model_name": "hog-mini", "ls_provider": "hanzoanalytics"},
metadata={"ls_model_name": "hog-mini", "ls_provider": "hanzo_insights"},
name="test",
)
expected = GenerationMetadata(
@@ -109,8 +109,8 @@ def test_metadata_capture(mock_client):
input=[{"role": "user", "content": "Who won the world series in 2020?"}],
start_time=1234567890,
model_params={"temperature": 0.5},
provider="hanzoanalytics",
base_url="https://us.hanzoanalytics.com",
provider="hanzo_insights",
base_url="https://us.hanzo_insights.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.hanzoanalytics.com",
base_url="https://test.hanzo_insights.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.hanzoanalytics.com"
assert generation_call["properties"]["$ai_base_url"] == "https://test.hanzo_insights.com"
def test_groups(mock_client):
@@ -1253,11 +1253,11 @@ def test_metadata_tools(mock_client):
with patch("time.time", return_value=1234567890):
callbacks._set_llm_metadata(
{"kwargs": {"openai_api_base": "https://us.hanzoanalytics.com"}},
{"kwargs": {"openai_api_base": "https://us.hanzo_insights.com"}},
run_id,
messages=[{"role": "user", "content": "What's the weather like in SF?"}],
invocation_params={"temperature": 0.5, "tools": tools},
metadata={"ls_model_name": "hog-mini", "ls_provider": "hanzoanalytics"},
metadata={"ls_model_name": "hog-mini", "ls_provider": "hanzo_insights"},
name="test",
)
expected = GenerationMetadata(
@@ -1265,8 +1265,8 @@ def test_metadata_tools(mock_client):
input=[{"role": "user", "content": "What's the weather like in SF?"}],
start_time=1234567890,
model_params={"temperature": 0.5},
provider="hanzoanalytics",
base_url="https://us.hanzoanalytics.com",
provider="hanzo_insights",
base_url="https://us.hanzo_insights.com",
name="test",
tools=tools,
end_time=None,
@@ -1868,7 +1868,7 @@ def test_openai_reasoning_tokens_o4_mini(mock_client):
def test_callback_handler_without_client():
"""Test that CallbackHandler works properly when no PostHog client is passed."""
with patch("hanzoanalytics.ai.langchain.callbacks.setup") as mock_setup:
with patch("hanzo_insights.ai.langchain.callbacks.setup") as mock_setup:
mock_client = mock_setup.return_value
callbacks = CallbackHandler()
@@ -1894,7 +1894,7 @@ def test_callback_handler_without_client():
def test_convert_message_to_dict_tool_calls():
"""Test that _convert_message_to_dict properly converts tool calls in AIMessage."""
from hanzoanalytics.ai.langchain.callbacks import _convert_message_to_dict
from hanzo_insights.ai.langchain.callbacks import _convert_message_to_dict
from langchain_core.messages import AIMessage
from langchain_core.messages.tool import ToolCall
@@ -2236,7 +2236,7 @@ def test_agent_action_and_finish_imports():
from langchain.schema.agent import AgentAction, AgentFinish # type: ignore
# Verify they're available in the callbacks module
from hanzoanalytics.ai.langchain.callbacks import CallbackHandler
from hanzo_insights.ai.langchain.callbacks import CallbackHandler
# Test on_agent_action with mock data
mock_client = MagicMock()
@@ -34,8 +34,8 @@ try:
ParsedResponseOutputText,
)
from hanzoanalytics.ai.openai import OpenAI
from hanzoanalytics.ai.openai.openai_async import AsyncOpenAI
from hanzo_insights.ai.openai import OpenAI
from hanzo_insights.ai.openai.openai_async import AsyncOpenAI
OPENAI_AVAILABLE = True
except ImportError:
@@ -49,7 +49,7 @@ pytestmark = pytest.mark.skipif(
@pytest.fixture
def mock_client():
with patch("hanzoanalytics.client.Client") as mock_client:
with patch("hanzo_insights.client.Client") as mock_client:
mock_client.privacy_mode = False
yield mock_client
@@ -1439,7 +1439,7 @@ def test_web_search_responses_api(mock_client):
"openai.resources.responses.Responses.create", return_value=mock_response
):
# Manually call the tracking since we're testing the converter logic
from hanzoanalytics.ai.utils import call_llm_and_track_usage
from hanzo_insights.ai.utils import call_llm_and_track_usage
def mock_create_call(**kwargs):
return mock_response
@@ -16,7 +16,7 @@ try:
TranscriptionSpanData,
)
from hanzoanalytics.ai.openai_agents import PostHogTracingProcessor, instrument
from hanzo_insights.ai.openai_agents import PostHogTracingProcessor, instrument
OPENAI_AGENTS_AVAILABLE = True
except ImportError:
@@ -33,7 +33,7 @@ pytestmark = pytest.mark.skipif(
def mock_client():
client = MagicMock()
client.privacy_mode = False
logging.getLogger("hanzoanalytics").setLevel(logging.DEBUG)
logging.getLogger("hanzo_insights").setLevel(logging.DEBUG)
return client
@@ -1,7 +1,7 @@
import unittest
from unittest.mock import MagicMock, patch
from hanzoanalytics.ai.prompts import Prompts
from hanzo_insights.ai.prompts import Prompts
class MockResponse:
@@ -36,7 +36,7 @@ class TestPrompts(unittest.TestCase):
self,
personal_api_key="phx_test_key",
project_api_key="phc_test_key",
host="https://us.hanzoanalytics.com",
host="https://us.hanzo_insights.com",
):
"""Create a mock PostHog client."""
mock = MagicMock()
@@ -49,7 +49,7 @@ class TestPrompts(unittest.TestCase):
class TestPromptsGet(TestPrompts):
"""Tests for the Prompts.get() method."""
@patch("hanzoanalytics.ai.prompts._get_session")
@patch("hanzo_insights.ai.prompts._get_session")
def test_successfully_fetch_a_prompt(self, mock_get_session):
"""Should successfully fetch a prompt."""
mock_get = mock_get_session.return_value.get
@@ -65,14 +65,14 @@ class TestPromptsGet(TestPrompts):
call_args = mock_get.call_args
self.assertEqual(
call_args[0][0],
"https://us.hanzoanalytics.com/api/environments/@current/llm_prompts/name/test-prompt/?token=phc_test_key",
"https://us.hanzo_insights.com/api/environments/@current/llm_prompts/name/test-prompt/?token=phc_test_key",
)
self.assertIn("Authorization", call_args[1]["headers"])
self.assertEqual(
call_args[1]["headers"]["Authorization"], "Bearer phx_test_key"
)
@patch("hanzoanalytics.ai.prompts._get_session")
@patch("hanzo_insights.ai.prompts._get_session")
def test_successfully_fetch_a_specific_prompt_version(self, mock_get_session):
"""Should successfully fetch a specific prompt version."""
mock_get = mock_get_session.return_value.get
@@ -93,11 +93,11 @@ class TestPromptsGet(TestPrompts):
call_args = mock_get.call_args
self.assertEqual(
call_args[0][0],
"https://us.hanzoanalytics.com/api/environments/@current/llm_prompts/name/test-prompt/?token=phc_test_key&version=1",
"https://us.hanzo_insights.com/api/environments/@current/llm_prompts/name/test-prompt/?token=phc_test_key&version=1",
)
@patch("hanzoanalytics.ai.prompts._get_session")
@patch("hanzoanalytics.ai.prompts.time.time")
@patch("hanzo_insights.ai.prompts._get_session")
@patch("hanzo_insights.ai.prompts.time.time")
def test_return_cached_prompt_when_fresh(self, mock_time, mock_get_session):
"""Should return cached prompt when fresh (no API call)."""
mock_get = mock_get_session.return_value.get
@@ -120,7 +120,7 @@ class TestPromptsGet(TestPrompts):
self.assertEqual(result2, self.mock_prompt_response["prompt"])
self.assertEqual(mock_get.call_count, 1) # No additional fetch
@patch("hanzoanalytics.ai.prompts._get_session")
@patch("hanzo_insights.ai.prompts._get_session")
def test_cache_latest_and_versioned_prompts_separately(self, mock_get_session):
"""Should cache latest and historical prompt versions separately."""
mock_get = mock_get_session.return_value.get
@@ -155,8 +155,8 @@ class TestPromptsGet(TestPrompts):
)
self.assertEqual(mock_get.call_count, 2)
@patch("hanzoanalytics.ai.prompts._get_session")
@patch("hanzoanalytics.ai.prompts.time.time")
@patch("hanzo_insights.ai.prompts._get_session")
@patch("hanzo_insights.ai.prompts.time.time")
def test_refetch_when_cache_is_stale(self, mock_time, mock_get_session):
"""Should refetch when cache is stale."""
mock_get = mock_get_session.return_value.get
@@ -187,9 +187,9 @@ class TestPromptsGet(TestPrompts):
self.assertEqual(result2, updated_prompt_response["prompt"])
self.assertEqual(mock_get.call_count, 2)
@patch("hanzoanalytics.ai.prompts._get_session")
@patch("hanzoanalytics.ai.prompts.time.time")
@patch("hanzoanalytics.ai.prompts.log")
@patch("hanzo_insights.ai.prompts._get_session")
@patch("hanzo_insights.ai.prompts.time.time")
@patch("hanzo_insights.ai.prompts.log")
def test_use_stale_cache_on_fetch_failure_with_warning(
self, mock_log, mock_time, mock_get_session
):
@@ -220,8 +220,8 @@ class TestPromptsGet(TestPrompts):
warning_call = mock_log.warning.call_args
self.assertIn("using stale cache", warning_call[0][0])
@patch("hanzoanalytics.ai.prompts._get_session")
@patch("hanzoanalytics.ai.prompts.log")
@patch("hanzo_insights.ai.prompts._get_session")
@patch("hanzo_insights.ai.prompts.log")
def test_use_fallback_when_no_cache_and_fetch_fails_with_warning(
self, mock_log, mock_get_session
):
@@ -242,7 +242,7 @@ class TestPromptsGet(TestPrompts):
warning_call = mock_log.warning.call_args
self.assertIn("using fallback", warning_call[0][0])
@patch("hanzoanalytics.ai.prompts._get_session")
@patch("hanzo_insights.ai.prompts._get_session")
def test_throw_when_no_cache_no_fallback_and_fetch_fails(self, mock_get_session):
"""Should throw when no cache, no fallback, and fetch fails."""
mock_get = mock_get_session.return_value.get
@@ -256,7 +256,7 @@ class TestPromptsGet(TestPrompts):
self.assertIn("Network error", str(context.exception))
@patch("hanzoanalytics.ai.prompts._get_session")
@patch("hanzo_insights.ai.prompts._get_session")
def test_handle_404_response(self, mock_get_session):
"""Should handle 404 response."""
mock_get = mock_get_session.return_value.get
@@ -270,7 +270,7 @@ class TestPromptsGet(TestPrompts):
self.assertIn('Prompt "nonexistent-prompt" not found', str(context.exception))
@patch("hanzoanalytics.ai.prompts._get_session")
@patch("hanzo_insights.ai.prompts._get_session")
def test_handle_404_response_for_specific_prompt_version(self, mock_get_session):
"""Should handle 404 response for a specific prompt version."""
mock_get = mock_get_session.return_value.get
@@ -287,7 +287,7 @@ class TestPromptsGet(TestPrompts):
str(context.exception),
)
@patch("hanzoanalytics.ai.prompts._get_session")
@patch("hanzo_insights.ai.prompts._get_session")
def test_handle_403_response(self, mock_get_session):
"""Should handle 403 response."""
mock_get = mock_get_session.return_value.get
@@ -327,7 +327,7 @@ class TestPromptsGet(TestPrompts):
"project_api_key is required to fetch prompts", str(context.exception)
)
@patch("hanzoanalytics.ai.prompts._get_session")
@patch("hanzo_insights.ai.prompts._get_session")
def test_throw_when_api_returns_invalid_response_format(self, mock_get_session):
"""Should throw when API returns invalid response format."""
mock_get = mock_get_session.return_value.get
@@ -341,13 +341,13 @@ class TestPromptsGet(TestPrompts):
self.assertIn("Invalid response format", str(context.exception))
@patch("hanzoanalytics.ai.prompts._get_session")
@patch("hanzo_insights.ai.prompts._get_session")
def test_use_custom_host_from_posthog_options(self, mock_get_session):
"""Should use custom host from PostHog options."""
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.hanzoanalytics.com")
posthog = self.create_mock_posthog(host="https://eu.hanzo_insights.com")
prompts = Prompts(posthog)
prompts.get("test-prompt")
@@ -355,13 +355,13 @@ class TestPromptsGet(TestPrompts):
call_args = mock_get.call_args
self.assertTrue(
call_args[0][0].startswith(
"https://eu.hanzoanalytics.com/api/environments/@current/llm_prompts/name/test-prompt/?token=phc_test_key"
"https://eu.hanzo_insights.com/api/environments/@current/llm_prompts/name/test-prompt/?token=phc_test_key"
),
f"Expected URL to start with 'https://eu.hanzoanalytics.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.hanzo_insights.com/api/environments/@current/llm_prompts/name/test-prompt/?token=phc_test_key', got {call_args[0][0]}",
)
@patch("hanzoanalytics.ai.prompts._get_session")
@patch("hanzoanalytics.ai.prompts.time.time")
@patch("hanzo_insights.ai.prompts._get_session")
@patch("hanzo_insights.ai.prompts.time.time")
def test_use_default_cache_ttl_5_minutes(self, mock_time, mock_get_session):
"""Should use default cache TTL (5 minutes) when not specified."""
mock_get = mock_get_session.return_value.get
@@ -389,8 +389,8 @@ class TestPromptsGet(TestPrompts):
prompts.get("test-prompt")
self.assertEqual(mock_get.call_count, 2)
@patch("hanzoanalytics.ai.prompts._get_session")
@patch("hanzoanalytics.ai.prompts.time.time")
@patch("hanzo_insights.ai.prompts._get_session")
@patch("hanzo_insights.ai.prompts.time.time")
def test_use_custom_default_cache_ttl_from_constructor(
self, mock_time, mock_get_session
):
@@ -413,7 +413,7 @@ class TestPromptsGet(TestPrompts):
prompts.get("test-prompt")
self.assertEqual(mock_get.call_count, 2)
@patch("hanzoanalytics.ai.prompts._get_session")
@patch("hanzo_insights.ai.prompts._get_session")
def test_url_encode_prompt_names_with_special_characters(self, mock_get_session):
"""Should URL-encode prompt names with special characters."""
mock_get = mock_get_session.return_value.get
@@ -427,10 +427,10 @@ class TestPromptsGet(TestPrompts):
call_args = mock_get.call_args
self.assertEqual(
call_args[0][0],
"https://us.hanzoanalytics.com/api/environments/@current/llm_prompts/name/prompt%20with%20spaces%2Fand%2Fslashes/?token=phc_test_key",
"https://us.hanzo_insights.com/api/environments/@current/llm_prompts/name/prompt%20with%20spaces%2Fand%2Fslashes/?token=phc_test_key",
)
@patch("hanzoanalytics.ai.prompts._get_session")
@patch("hanzo_insights.ai.prompts._get_session")
def test_work_with_direct_options_no_posthog_client(self, mock_get_session):
"""Should work with direct options (no PostHog client)."""
mock_get = mock_get_session.return_value.get
@@ -446,13 +446,13 @@ class TestPromptsGet(TestPrompts):
call_args = mock_get.call_args
self.assertEqual(
call_args[0][0],
"https://us.hanzoanalytics.com/api/environments/@current/llm_prompts/name/test-prompt/?token=phc_direct_key",
"https://us.hanzo_insights.com/api/environments/@current/llm_prompts/name/test-prompt/?token=phc_direct_key",
)
self.assertEqual(
call_args[1]["headers"]["Authorization"], "Bearer phx_direct_key"
)
@patch("hanzoanalytics.ai.prompts._get_session")
@patch("hanzo_insights.ai.prompts._get_session")
def test_use_custom_host_from_direct_options(self, mock_get_session):
"""Should use custom host from direct options."""
mock_get = mock_get_session.return_value.get
@@ -461,7 +461,7 @@ class TestPromptsGet(TestPrompts):
prompts = Prompts(
personal_api_key="phx_direct_key",
project_api_key="phc_direct_key",
host="https://eu.hanzoanalytics.com",
host="https://eu.hanzo_insights.com",
)
prompts.get("test-prompt")
@@ -469,11 +469,11 @@ class TestPromptsGet(TestPrompts):
call_args = mock_get.call_args
self.assertEqual(
call_args[0][0],
"https://eu.hanzoanalytics.com/api/environments/@current/llm_prompts/name/test-prompt/?token=phc_direct_key",
"https://eu.hanzo_insights.com/api/environments/@current/llm_prompts/name/test-prompt/?token=phc_direct_key",
)
@patch("hanzoanalytics.ai.prompts._get_session")
@patch("hanzoanalytics.ai.prompts.time.time")
@patch("hanzo_insights.ai.prompts._get_session")
@patch("hanzo_insights.ai.prompts.time.time")
def test_use_custom_default_cache_ttl_from_direct_options(
self, mock_time, mock_get_session
):
@@ -628,7 +628,7 @@ class TestPromptsClearCache(TestPrompts):
self.assertIn("requires 'name'", str(context.exception))
@patch("hanzoanalytics.ai.prompts._get_session")
@patch("hanzo_insights.ai.prompts._get_session")
def test_clear_a_specific_prompt_from_cache(self, mock_get_session):
"""Should clear a specific prompt from cache."""
mock_get = mock_get_session.return_value.get
@@ -659,7 +659,7 @@ class TestPromptsClearCache(TestPrompts):
prompts.get("other-prompt")
self.assertEqual(mock_get.call_count, 3)
@patch("hanzoanalytics.ai.prompts._get_session")
@patch("hanzo_insights.ai.prompts._get_session")
def test_clear_a_specific_prompt_version_from_cache(self, mock_get_session):
"""Should clear only the requested prompt version from cache."""
mock_get = mock_get_session.return_value.get
@@ -695,7 +695,7 @@ class TestPromptsClearCache(TestPrompts):
prompts.get("test-prompt", version=1)
self.assertEqual(mock_get.call_count, 3)
@patch("hanzoanalytics.ai.prompts._get_session")
@patch("hanzo_insights.ai.prompts._get_session")
def test_clear_a_prompt_name_clears_all_cached_versions(self, mock_get_session):
"""Should clear latest and versioned cache entries for the same prompt name."""
mock_get = mock_get_session.return_value.get
@@ -730,7 +730,7 @@ class TestPromptsClearCache(TestPrompts):
prompts.get("test-prompt", version=1)
self.assertEqual(mock_get.call_count, 4)
@patch("hanzoanalytics.ai.prompts._get_session")
@patch("hanzo_insights.ai.prompts._get_session")
def test_clear_all_prompts_from_cache(self, mock_get_session):
"""Should clear all prompts from cache when no name is provided."""
mock_get = mock_get_session.return_value.get
@@ -1,7 +1,7 @@
import os
import unittest
from hanzoanalytics.ai.sanitization import (
from hanzo_insights.ai.sanitization import (
redact_base64_data_url,
sanitize_openai,
sanitize_openai_response,
@@ -13,8 +13,8 @@ import time
import unittest
from unittest.mock import MagicMock, patch
from hanzoanalytics.client import Client
from hanzoanalytics.test.test_utils import FAKE_TEST_API_KEY
from hanzo_insights.client import Client
from hanzo_insights.test.test_utils import FAKE_TEST_API_KEY
class TestSystemPromptCapture(unittest.TestCase):
@@ -61,7 +61,7 @@ class TestSystemPromptCapture(unittest.TestCase):
from openai.types.chat.chat_completion import Choice
from openai.types.completion_usage import CompletionUsage
from hanzoanalytics.ai.openai import OpenAI
from hanzo_insights.ai.openai import OpenAI
except ImportError:
self.skipTest("OpenAI package not available")
@@ -110,7 +110,7 @@ class TestSystemPromptCapture(unittest.TestCase):
from openai.types.chat.chat_completion import Choice
from openai.types.completion_usage import CompletionUsage
from hanzoanalytics.ai.openai import OpenAI
from hanzo_insights.ai.openai import OpenAI
except ImportError:
self.skipTest("OpenAI package not available")
@@ -162,7 +162,7 @@ class TestSystemPromptCapture(unittest.TestCase):
from openai.types.chat.chat_completion_chunk import Choice as ChoiceChunk
from openai.types.completion_usage import CompletionUsage
from hanzoanalytics.ai.openai import OpenAI
from hanzo_insights.ai.openai import OpenAI
except ImportError:
self.skipTest("OpenAI package not available")
@@ -223,7 +223,7 @@ class TestSystemPromptCapture(unittest.TestCase):
def test_anthropic_messages_array_system_prompt(self):
"""Test Anthropic with system prompt in messages array."""
try:
from hanzoanalytics.ai.anthropic import Anthropic
from hanzo_insights.ai.anthropic import Anthropic
except ImportError:
self.skipTest("Anthropic package not available")
@@ -255,7 +255,7 @@ class TestSystemPromptCapture(unittest.TestCase):
def test_anthropic_separate_system_parameter(self):
"""Test Anthropic with system prompt as separate parameter."""
try:
from hanzoanalytics.ai.anthropic import Anthropic
from hanzo_insights.ai.anthropic import Anthropic
except ImportError:
self.skipTest("Anthropic package not available")
@@ -286,7 +286,7 @@ class TestSystemPromptCapture(unittest.TestCase):
def test_gemini_contents_array_system_prompt(self):
"""Test Gemini with system prompt in contents array."""
try:
from hanzoanalytics.ai.gemini import Client
from hanzo_insights.ai.gemini import Client
except ImportError:
self.skipTest("Gemini package not available")
@@ -326,7 +326,7 @@ class TestSystemPromptCapture(unittest.TestCase):
def test_gemini_system_instruction_parameter(self):
"""Test Gemini with system_instruction in config parameter."""
try:
from hanzoanalytics.ai.gemini import Client
from hanzo_insights.ai.gemini import Client
except ImportError:
self.skipTest("Gemini package not available")
@@ -1,6 +1,6 @@
from parameterized import parameterized
from hanzoanalytics.ai.utils import _get_tokens_source
from hanzo_insights.ai.utils import _get_tokens_source
@parameterized.expand(
@@ -1,4 +1,4 @@
from hanzoanalytics.contexts import (
from hanzo_insights.contexts import (
new_context,
get_context_session_id,
get_context_distinct_id,
@@ -20,7 +20,7 @@ if not settings.configured:
)
django.setup()
from hanzoanalytics.integrations.django import PosthogContextMiddleware
from hanzo_insights.integrations.django import PosthogContextMiddleware
class MockRequest:
@@ -2,16 +2,16 @@ import unittest
import mock
from hanzoanalytics.client import Client
from hanzoanalytics.test.test_utils import FAKE_TEST_API_KEY
from hanzo_insights.client import Client
from hanzo_insights.test.test_utils import FAKE_TEST_API_KEY
class TestClient(unittest.TestCase):
@classmethod
def setUpClass(cls):
# This ensures no real HTTP POST requests are made
cls.client_post_patcher = mock.patch("hanzoanalytics.client.batch_post")
cls.consumer_post_patcher = mock.patch("hanzoanalytics.consumer.batch_post")
cls.client_post_patcher = mock.patch("hanzo_insights.client.batch_post")
cls.consumer_post_patcher = mock.patch("hanzo_insights.consumer.batch_post")
cls.client_post_patcher.start()
cls.consumer_post_patcher.start()
@@ -40,7 +40,7 @@ class TestClient(unittest.TestCase):
event["properties"]["processed_by_before_send"] = True
return event
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(
FAKE_TEST_API_KEY,
on_error=self.set_fail,
@@ -73,7 +73,7 @@ class TestClient(unittest.TestCase):
return None
return event
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(
FAKE_TEST_API_KEY,
on_error=self.set_fail,
@@ -101,7 +101,7 @@ class TestClient(unittest.TestCase):
def buggy_before_send(event):
raise ValueError("Oops!")
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(
FAKE_TEST_API_KEY,
on_error=self.set_fail,
@@ -128,7 +128,7 @@ class TestClient(unittest.TestCase):
event["properties"]["marked"] = True
return event
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(
FAKE_TEST_API_KEY,
on_error=self.set_fail,
@@ -153,7 +153,7 @@ class TestClient(unittest.TestCase):
def test_before_send_callback_disabled_when_none(self):
"""Test that client works normally when before_send is None."""
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(
FAKE_TEST_API_KEY,
on_error=self.set_fail,
@@ -189,7 +189,7 @@ class TestClient(unittest.TestCase):
return event
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(
FAKE_TEST_API_KEY,
on_error=self.set_fail,
@@ -7,21 +7,21 @@ import mock
import six
from parameterized import parameterized
from hanzoanalytics.client import Client
from hanzoanalytics.contexts import get_context_session_id, new_context, set_context_session
from hanzoanalytics.request import APIError, GetResponse
from hanzoanalytics.test.test_utils import FAKE_TEST_API_KEY
from hanzoanalytics.types import FeatureFlag, LegacyFlagMetadata
from hanzoanalytics.version import VERSION
from hanzoanalytics.contexts import tag
from hanzo_insights.client import Client
from hanzo_insights.contexts import get_context_session_id, new_context, set_context_session
from hanzo_insights.request import APIError, GetResponse
from hanzo_insights.test.test_utils import FAKE_TEST_API_KEY
from hanzo_insights.types import FeatureFlag, LegacyFlagMetadata
from hanzo_insights.version import VERSION
from hanzo_insights.contexts import tag
class TestClient(unittest.TestCase):
@classmethod
def setUpClass(cls):
# This ensures no real HTTP POST requests are made
cls.client_post_patcher = mock.patch("hanzoanalytics.client.batch_post")
cls.consumer_post_patcher = mock.patch("hanzoanalytics.consumer.batch_post")
cls.client_post_patcher = mock.patch("hanzo_insights.client.batch_post")
cls.consumer_post_patcher = mock.patch("hanzo_insights.consumer.batch_post")
cls.client_post_patcher.start()
cls.consumer_post_patcher.start()
@@ -46,7 +46,7 @@ class TestClient(unittest.TestCase):
self.client.flush()
def test_basic_capture(self):
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, sync_mode=True)
msg_uuid = client.capture("python test event", distinct_id="distinct_id")
self.assertIsNotNone(msg_uuid)
@@ -70,7 +70,7 @@ class TestClient(unittest.TestCase):
assert msg["properties"]["$os_version"] == mock.ANY
def test_basic_capture_with_uuid(self):
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, sync_mode=True)
uuid = str(uuid4())
msg_uuid = client.capture(
@@ -92,7 +92,7 @@ class TestClient(unittest.TestCase):
self.assertEqual(msg["properties"]["$lib_version"], VERSION)
def test_basic_capture_with_project_api_key(self):
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(
project_api_key=FAKE_TEST_API_KEY,
on_error=self.set_fail,
@@ -115,7 +115,7 @@ class TestClient(unittest.TestCase):
self.assertEqual(msg["properties"]["$lib_version"], VERSION)
def test_basic_super_properties(self):
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(
FAKE_TEST_API_KEY,
super_properties={"source": "repo-name"},
@@ -175,7 +175,7 @@ class TestClient(unittest.TestCase):
client = Client(
FAKE_TEST_API_KEY,
on_error=self.set_fail,
host="https://app.hanzoanalytics.com",
host="https://app.hanzo_insights.com",
)
exception = Exception("test exception")
client.capture_exception(exception, distinct_id="distinct_id")
@@ -242,7 +242,7 @@ class TestClient(unittest.TestCase):
capture_call[1]["properties"]["$exception_list"][0]["stacktrace"][
"frames"
][0]["module"],
"hanzoanalytics.test.test_client",
"hanzo_insights.test.test_client",
)
self.assertEqual(
capture_call[1]["properties"]["$exception_list"][0]["stacktrace"][
@@ -253,7 +253,7 @@ class TestClient(unittest.TestCase):
def test_basic_capture_exception_with_no_exception_happening(self):
with mock.patch.object(Client, "capture", return_value=None) as patch_capture:
with self.assertLogs("hanzoanalytics", level="WARNING") as logs:
with self.assertLogs("hanzo_insights", level="WARNING") as logs:
client = self.client
client.capture_exception(None)
@@ -265,7 +265,7 @@ class TestClient(unittest.TestCase):
def test_capture_exception_logs_when_enabled(self):
client = Client(FAKE_TEST_API_KEY, log_captured_exceptions=True)
with self.assertLogs("hanzoanalytics", level="ERROR") as logs:
with self.assertLogs("hanzo_insights", level="ERROR") as logs:
client.capture_exception(
Exception("test exception"), distinct_id="distinct_id"
)
@@ -273,11 +273,11 @@ class TestClient(unittest.TestCase):
logs.output[0], "ERROR:posthog:test exception\nNoneType: None"
)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_basic_capture_with_feature_flags(self, patch_flags):
patch_flags.return_value = {"featureFlags": {"beta-feature": "random-variant"}}
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(
FAKE_TEST_API_KEY,
on_error=self.set_fail,
@@ -310,7 +310,7 @@ class TestClient(unittest.TestCase):
self.assertEqual(patch_flags.call_count, 1)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_basic_capture_with_locally_evaluated_feature_flags(self, patch_flags):
patch_flags.return_value = {"featureFlags": {"beta-feature": "random-variant"}}
@@ -327,7 +327,7 @@ class TestClient(unittest.TestCase):
{
"key": "email",
"type": "person",
"value": "test@hanzoanalytics.com",
"value": "test@hanzo_insights.com",
"operator": "exact",
}
],
@@ -400,7 +400,7 @@ class TestClient(unittest.TestCase):
},
}
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(
FAKE_TEST_API_KEY,
on_error=self.set_fail,
@@ -438,7 +438,7 @@ class TestClient(unittest.TestCase):
self.assertEqual(patch_flags.call_count, 0)
# test that flags are not evaluated without local evaluation
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(
FAKE_TEST_API_KEY,
on_error=self.set_fail,
@@ -460,7 +460,7 @@ class TestClient(unittest.TestCase):
assert "$feature/false-flag" not in msg["properties"]
assert "$active_feature_flags" not in msg["properties"]
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.get")
def test_load_feature_flags_quota_limited(self, patch_get):
mock_response = {
"type": "quota_limited",
@@ -470,7 +470,7 @@ class TestClient(unittest.TestCase):
patch_get.side_effect = APIError(402, mock_response["detail"])
client = Client(FAKE_TEST_API_KEY, personal_api_key="test")
with self.assertLogs("hanzoanalytics", level="WARNING") as logs:
with self.assertLogs("hanzo_insights", level="WARNING") as logs:
client._load_feature_flags()
self.assertEqual(client.feature_flags, [])
@@ -479,12 +479,12 @@ class TestClient(unittest.TestCase):
self.assertEqual(client.cohorts, {})
self.assertIn("PostHog feature flags quota limited", logs.output[0])
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.get")
def test_load_feature_flags_unauthorized(self, patch_get):
patch_get.side_effect = APIError(401, "Unauthorized")
client = Client(FAKE_TEST_API_KEY, personal_api_key="test")
with self.assertLogs("hanzoanalytics", level="ERROR") as logs:
with self.assertLogs("hanzo_insights", level="ERROR") as logs:
client._load_feature_flags()
self.assertEqual(client.feature_flags, [])
@@ -493,7 +493,7 @@ class TestClient(unittest.TestCase):
self.assertEqual(client.cohorts, {})
self.assertIn("please set a valid personal_api_key", logs.output[0])
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_dont_override_capture_with_local_flags(self, patch_flags):
patch_flags.return_value = {"featureFlags": {"beta-feature": "random-variant"}}
@@ -510,7 +510,7 @@ class TestClient(unittest.TestCase):
{
"key": "email",
"type": "person",
"value": "test@hanzoanalytics.com",
"value": "test@hanzo_insights.com",
"operator": "exact",
}
],
@@ -568,7 +568,7 @@ class TestClient(unittest.TestCase):
},
}
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(
FAKE_TEST_API_KEY,
on_error=self.set_fail,
@@ -608,7 +608,7 @@ class TestClient(unittest.TestCase):
self.assertEqual(patch_flags.call_count, 0)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_basic_capture_with_feature_flags_returns_active_only(self, patch_flags):
patch_flags.return_value = {
"featureFlags": {
@@ -618,7 +618,7 @@ class TestClient(unittest.TestCase):
}
}
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(
FAKE_TEST_API_KEY,
on_error=self.set_fail,
@@ -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.hanzoanalytics.com",
"https://us.i.hanzo_insights.com",
timeout=3,
distinct_id="distinct_id",
groups={},
@@ -665,7 +665,7 @@ class TestClient(unittest.TestCase):
device_id=None,
)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_basic_capture_with_feature_flags_and_disable_geoip_returns_correctly(
self, patch_flags
):
@@ -677,10 +677,10 @@ class TestClient(unittest.TestCase):
}
}
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(
FAKE_TEST_API_KEY,
host="https://app.hanzoanalytics.com",
host="https://app.hanzo_insights.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.hanzoanalytics.com",
"https://us.i.hanzo_insights.com",
timeout=12,
distinct_id="distinct_id",
groups={},
@@ -730,13 +730,13 @@ class TestClient(unittest.TestCase):
device_id=None,
)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_basic_capture_with_feature_flags_switched_off_doesnt_send_them(
self, patch_flags
):
patch_flags.return_value = {"featureFlags": {"beta-feature": "random-variant"}}
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(
FAKE_TEST_API_KEY,
on_error=self.set_fail,
@@ -765,7 +765,7 @@ class TestClient(unittest.TestCase):
self.assertEqual(patch_flags.call_count, 0)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_capture_with_send_feature_flags_false_and_local_evaluation_doesnt_send_flags(
self, patch_flags
):
@@ -814,7 +814,7 @@ class TestClient(unittest.TestCase):
},
}
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(
FAKE_TEST_API_KEY,
on_error=self.set_fail,
@@ -847,7 +847,7 @@ class TestClient(unittest.TestCase):
# CRITICAL: Verify the /flags API was NOT called
self.assertEqual(patch_flags.call_count, 0)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_capture_with_send_feature_flags_true_and_local_evaluation_uses_local_flags(
self, patch_flags
):
@@ -896,7 +896,7 @@ class TestClient(unittest.TestCase):
},
}
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(
FAKE_TEST_API_KEY,
on_error=self.set_fail,
@@ -937,12 +937,12 @@ class TestClient(unittest.TestCase):
# CRITICAL: Verify the /flags API was NOT called
self.assertEqual(patch_flags.call_count, 0)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_capture_with_send_feature_flags_options_only_evaluate_locally_true(
self, patch_flags
):
"""Test that SendFeatureFlagsOptions with only_evaluate_locally=True uses local evaluation"""
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(
FAKE_TEST_API_KEY,
on_error=self.set_fail,
@@ -990,14 +990,14 @@ class TestClient(unittest.TestCase):
self.assertEqual(msg["properties"]["$feature/local-flag"], True)
self.assertEqual(msg["properties"]["$active_feature_flags"], ["local-flag"])
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_capture_with_send_feature_flags_options_only_evaluate_locally_false(
self, patch_flags
):
"""Test that SendFeatureFlagsOptions with only_evaluate_locally=False forces remote evaluation"""
patch_flags.return_value = {"featureFlags": {"remote-flag": "remote-value"}}
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(
FAKE_TEST_API_KEY,
on_error=self.set_fail,
@@ -1036,14 +1036,14 @@ class TestClient(unittest.TestCase):
self.assertEqual(msg["properties"]["$feature/remote-flag"], "remote-value")
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_capture_with_send_feature_flags_options_default_behavior(
self, patch_flags
):
"""Test that SendFeatureFlagsOptions without only_evaluate_locally defaults to remote evaluation"""
patch_flags.return_value = {"featureFlags": {"default-flag": "default-value"}}
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(
FAKE_TEST_API_KEY,
on_error=self.set_fail,
@@ -1076,12 +1076,12 @@ class TestClient(unittest.TestCase):
msg["properties"]["$feature/default-flag"], "default-value"
)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_capture_exception_with_send_feature_flags_options(self, patch_flags):
"""Test that capture_exception also supports SendFeatureFlagsOptions"""
patch_flags.return_value = {"featureFlags": {"exception-flag": True}}
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(
FAKE_TEST_API_KEY,
on_error=self.set_fail,
@@ -1120,7 +1120,7 @@ class TestClient(unittest.TestCase):
def test_stringifies_distinct_id(self):
# A large number that loses precision in node:
# node -e "console.log(157963456373623802 + 1)" > 157963456373623800
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, sync_mode=True)
msg_uuid = client.capture(
"python test event", distinct_id=157963456373623802
@@ -1136,7 +1136,7 @@ class TestClient(unittest.TestCase):
self.assertEqual(msg["distinct_id"], "157963456373623802")
def test_advanced_capture(self):
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, sync_mode=True)
msg_uuid = client.capture(
"python test event",
@@ -1163,12 +1163,12 @@ class TestClient(unittest.TestCase):
self.assertTrue("$groups" not in msg["properties"])
def test_groups_capture(self):
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, sync_mode=True)
msg_uuid = client.capture(
"test_event",
distinct_id="distinct_id",
groups={"company": "id:5", "instance": "app.hanzoanalytics.com"},
groups={"company": "id:5", "instance": "app.hanzo_insights.com"},
)
self.assertIsNotNone(msg_uuid)
@@ -1180,11 +1180,11 @@ class TestClient(unittest.TestCase):
self.assertEqual(
msg["properties"]["$groups"],
{"company": "id:5", "instance": "app.hanzoanalytics.com"},
{"company": "id:5", "instance": "app.hanzo_insights.com"},
)
def test_basic_set(self):
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, sync_mode=True)
msg_uuid = client.set(
distinct_id="distinct_id", properties={"trait": "value"}
@@ -1203,7 +1203,7 @@ class TestClient(unittest.TestCase):
self.assertEqual(msg["distinct_id"], "distinct_id")
def test_advanced_set(self):
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, sync_mode=True)
msg_uuid = client.set(
distinct_id="distinct_id",
@@ -1228,7 +1228,7 @@ class TestClient(unittest.TestCase):
self.assertEqual(msg["distinct_id"], "distinct_id")
def test_basic_set_once(self):
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, sync_mode=True)
msg_uuid = client.set_once(
distinct_id="distinct_id", properties={"trait": "value"}
@@ -1247,7 +1247,7 @@ class TestClient(unittest.TestCase):
self.assertEqual(msg["distinct_id"], "distinct_id")
def test_advanced_set_once(self):
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, sync_mode=True)
msg_uuid = client.set_once(
distinct_id="distinct_id",
@@ -1272,7 +1272,7 @@ class TestClient(unittest.TestCase):
self.assertEqual(msg["distinct_id"], "distinct_id")
def test_basic_group_identify(self):
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, sync_mode=True)
msg_uuid = client.group_identify("organization", "id:5")
@@ -1299,7 +1299,7 @@ class TestClient(unittest.TestCase):
self.assertIsNotNone(msg.get("uuid"))
def test_basic_group_identify_with_distinct_id(self):
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, sync_mode=True)
msg_uuid = client.group_identify(
"organization", "id:5", distinct_id="distinct_id"
@@ -1328,7 +1328,7 @@ class TestClient(unittest.TestCase):
self.assertIsNotNone(msg.get("uuid"))
def test_advanced_group_identify(self):
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, sync_mode=True)
msg_uuid = client.group_identify(
"organization",
@@ -1360,7 +1360,7 @@ class TestClient(unittest.TestCase):
self.assertEqual(msg["timestamp"], "2014-09-03T00:00:00+00:00")
def test_advanced_group_identify_with_distinct_id(self):
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, sync_mode=True)
msg_uuid = client.group_identify(
"organization",
@@ -1395,7 +1395,7 @@ class TestClient(unittest.TestCase):
self.assertEqual(msg["timestamp"], "2014-09-03T00:00:00+00:00")
def test_basic_alias(self):
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, sync_mode=True)
msg_uuid = client.alias("previousId", "distinct_id")
self.assertIsNotNone(msg_uuid)
@@ -1435,7 +1435,7 @@ class TestClient(unittest.TestCase):
def test_capture_with_session_id_variations(
self, test_name, session_id, additional_properties, expected_properties
):
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, sync_mode=True)
properties = {"$session_id": session_id, **additional_properties}
@@ -1462,7 +1462,7 @@ class TestClient(unittest.TestCase):
self.assertEqual(msg["properties"][key], value)
def test_session_id_preserved_with_groups(self):
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, sync_mode=True)
session_id = "group-session-101"
@@ -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.hanzoanalytics.com"},
groups={"company": "id:5", "instance": "app.hanzo_insights.com"},
)
self.assertIsNotNone(msg_uuid)
@@ -1483,11 +1483,11 @@ class TestClient(unittest.TestCase):
self.assertEqual(msg["properties"]["$session_id"], session_id)
self.assertEqual(
msg["properties"]["$groups"],
{"company": "id:5", "instance": "app.hanzoanalytics.com"},
{"company": "id:5", "instance": "app.hanzo_insights.com"},
)
def test_session_id_with_anonymous_event(self):
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, sync_mode=True)
session_id = "anonymous-session-202"
@@ -1584,7 +1584,7 @@ class TestClient(unittest.TestCase):
additional_properties,
expected_additional_properties,
):
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, sync_mode=True)
properties = {"$session_id": session_id, **additional_properties}
@@ -1651,7 +1651,7 @@ class TestClient(unittest.TestCase):
expected_session_id,
expected_super_props,
):
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(
FAKE_TEST_API_KEY, super_properties=super_properties, sync_mode=True
)
@@ -1704,7 +1704,7 @@ class TestClient(unittest.TestCase):
self.assertFalse(consumer.is_alive())
def test_synchronous(self):
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(FAKE_TEST_API_KEY, sync_mode=True)
msg_uuid = client.capture("test event", distinct_id="distinct_id")
@@ -1758,7 +1758,7 @@ class TestClient(unittest.TestCase):
# the post function should be called 2 times, with a batch size of 10
# each time.
with mock.patch(
"hanzoanalytics.consumer.batch_post", side_effect=mock_post_fn
"hanzo_insights.consumer.batch_post", side_effect=mock_post_fn
) as mock_post:
for _ in range(20):
client.capture(
@@ -1784,7 +1784,7 @@ class TestClient(unittest.TestCase):
self.assertIsNone(msg_uuid)
self.assertFalse(self.failed)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_disabled_with_feature_flags(self, patch_flags):
client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, disabled=True)
@@ -1812,7 +1812,7 @@ class TestClient(unittest.TestCase):
self.assertTrue(client.queue.empty())
def test_enabled_to_disabled(self):
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(
FAKE_TEST_API_KEY,
on_error=self.set_fail,
@@ -1836,7 +1836,7 @@ class TestClient(unittest.TestCase):
self.assertFalse(self.failed)
def test_disable_geoip_default_on_events(self):
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(
FAKE_TEST_API_KEY,
on_error=self.set_fail,
@@ -1853,7 +1853,7 @@ class TestClient(unittest.TestCase):
self.assertEqual(capture_msg["properties"]["$geoip_disable"], True)
def test_disable_geoip_override_on_events(self):
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(
FAKE_TEST_API_KEY,
on_error=self.set_fail,
@@ -1889,7 +1889,7 @@ class TestClient(unittest.TestCase):
self.assertEqual("$geoip_disable" not in identify_msg["properties"], True)
def test_disable_geoip_method_overrides_init_on_events(self):
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(
FAKE_TEST_API_KEY,
on_error=self.set_fail,
@@ -1907,7 +1907,7 @@ class TestClient(unittest.TestCase):
msg = batch_data[0]
self.assertTrue("$geoip_disable" not in msg["properties"])
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_disable_geoip_default_on_decide(self, patch_flags):
patch_flags.return_value = {
"featureFlags": {
@@ -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.hanzoanalytics.com",
"https://us.i.hanzo_insights.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.hanzoanalytics.com",
"https://us.i.hanzo_insights.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.hanzoanalytics.com",
"https://us.i.hanzo_insights.com",
timeout=3,
distinct_id="all_flags_payloads_id",
groups={},
@@ -1960,8 +1960,8 @@ class TestClient(unittest.TestCase):
device_id=None,
)
@mock.patch("hanzoanalytics.client.Poller")
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.Poller")
@mock.patch("hanzo_insights.client.get")
def test_call_identify_fails(self, patch_get, patch_poller):
def raise_effect():
raise Exception("http exception")
@@ -1972,7 +1972,7 @@ class TestClient(unittest.TestCase):
self.assertFalse(client.feature_enabled("example", "distinct_id"))
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_default_properties_get_added_properly(self, patch_flags):
patch_flags.return_value = {
"featureFlags": {
@@ -1983,27 +1983,27 @@ class TestClient(unittest.TestCase):
}
client = Client(
FAKE_TEST_API_KEY,
host="http://app2.hanzoanalytics.com",
host="http://app2.hanzo_insights.com",
on_error=self.set_fail,
disable_geoip=False,
)
client.get_feature_flag(
"random_key",
"some_id",
groups={"company": "id:5", "instance": "app.hanzoanalytics.com"},
groups={"company": "id:5", "instance": "app.hanzo_insights.com"},
person_properties={"x1": "y1"},
group_properties={"company": {"x": "y"}},
)
patch_flags.assert_called_with(
"random_key",
"http://app2.hanzoanalytics.com",
"http://app2.hanzo_insights.com",
timeout=3,
distinct_id="some_id",
groups={"company": "id:5", "instance": "app.hanzoanalytics.com"},
groups={"company": "id:5", "instance": "app.hanzo_insights.com"},
person_properties={"distinct_id": "some_id", "x1": "y1"},
group_properties={
"company": {"$group_key": "id:5", "x": "y"},
"instance": {"$group_key": "app.hanzoanalytics.com"},
"instance": {"$group_key": "app.hanzo_insights.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.hanzoanalytics.com"},
groups={"company": "id:5", "instance": "app.hanzo_insights.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.hanzoanalytics.com",
"http://app2.hanzo_insights.com",
timeout=3,
distinct_id="some_id",
groups={"company": "id:5", "instance": "app.hanzoanalytics.com"},
groups={"company": "id:5", "instance": "app.hanzo_insights.com"},
person_properties={"distinct_id": "override"},
group_properties={
"company": {"$group_key": "group_override"},
"instance": {"$group_key": "app.hanzoanalytics.com"},
"instance": {"$group_key": "app.hanzo_insights.com"},
},
geoip_disable=False,
device_id=None,
@@ -2045,7 +2045,7 @@ class TestClient(unittest.TestCase):
)
patch_flags.assert_called_with(
"random_key",
"http://app2.hanzoanalytics.com",
"http://app2.hanzo_insights.com",
timeout=3,
distinct_id="some_id",
groups={},
@@ -2080,7 +2080,7 @@ class TestClient(unittest.TestCase):
("get_flags_decision", ["some_id"], {}, None),
]
)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_device_id_is_passed_to_flags_request(
self,
method,
@@ -2107,13 +2107,13 @@ class TestClient(unittest.TestCase):
expected_call["flag_keys_to_evaluate"] = expected_flag_keys
patch_flags.assert_called_with(
"random_key", "https://us.i.hanzoanalytics.com", timeout=3, **expected_call
"random_key", "https://us.i.hanzo_insights.com", timeout=3, **expected_call
)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_device_id_from_context_is_used_in_flags_request(self, patch_flags):
"""Test that device_id from context is used in flags request when not explicitly provided."""
from hanzoanalytics.contexts import new_context, set_context_device_id
from hanzo_insights.contexts import new_context, set_context_device_id
patch_flags.return_value = {
"featureFlags": {
@@ -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.hanzoanalytics.com",
"https://us.i.hanzo_insights.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.hanzoanalytics.com",
"https://us.i.hanzo_insights.com",
timeout=3,
distinct_id="some_id",
groups={},
@@ -2217,8 +2217,8 @@ class TestClient(unittest.TestCase):
distro_info,
):
"""Test that we can mock platform and sys for testing system_context"""
with mock.patch("hanzoanalytics.utils.platform") as mock_platform:
with mock.patch("hanzoanalytics.utils.sys") as mock_sys:
with mock.patch("hanzo_insights.utils.platform") as mock_platform:
with mock.patch("hanzo_insights.utils.sys") as mock_sys:
# Set up common mocks
mock_platform.python_implementation.return_value = expected_runtime
mock_sys.version_info = version_info
@@ -2234,15 +2234,15 @@ class TestClient(unittest.TestCase):
if sys_platform == "linux":
# Directly patch the get_os_info function to return our expected values
with mock.patch(
"hanzoanalytics.utils.get_os_info",
"hanzo_insights.utils.get_os_info",
return_value=(expected_os, expected_os_version),
):
from hanzoanalytics.utils import system_context
from hanzo_insights.utils import system_context
context = system_context()
else:
# Get system context for non-Linux platforms
from hanzoanalytics.utils import system_context
from hanzo_insights.utils import system_context
context = system_context()
@@ -2256,7 +2256,7 @@ class TestClient(unittest.TestCase):
assert context == expected_context
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_get_decide_returns_normalized_decide_response(self, patch_flags):
patch_flags.return_value = {
"featureFlags": {
@@ -2311,7 +2311,7 @@ class TestClient(unittest.TestCase):
}
def test_set_context_session_with_capture(self):
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, sync_mode=True)
with new_context():
set_context_session("context-session-123")
@@ -2334,7 +2334,7 @@ class TestClient(unittest.TestCase):
)
def test_set_context_session_with_page_explicit_properties(self):
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, sync_mode=True)
with new_context():
set_context_session("page-explicit-session-789")
@@ -2360,9 +2360,9 @@ class TestClient(unittest.TestCase):
def test_set_context_session_override_in_capture(self):
"""Test that explicit session ID overrides context session ID in capture"""
from hanzoanalytics.contexts import new_context, set_context_session
from hanzo_insights.contexts import new_context, set_context_session
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, sync_mode=True)
with new_context():
set_context_session("context-session-override")
@@ -2387,8 +2387,8 @@ class TestClient(unittest.TestCase):
msg["properties"]["$session_id"], "explicit-session-override"
)
@mock.patch("hanzoanalytics.client.Poller")
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.Poller")
@mock.patch("hanzo_insights.client.get")
def test_enable_local_evaluation_false_disables_poller(
self, patch_get, patch_poller
):
@@ -2425,8 +2425,8 @@ class TestClient(unittest.TestCase):
self.assertEqual(len(client.feature_flags), 1)
self.assertEqual(client.feature_flags[0]["key"], "beta-feature")
@mock.patch("hanzoanalytics.client.Poller")
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.Poller")
@mock.patch("hanzo_insights.client.get")
def test_enable_local_evaluation_true_starts_poller(self, patch_get, patch_poller):
"""Test that when enable_local_evaluation=True (default), the poller is started"""
patch_get.return_value = GetResponse(
@@ -2460,7 +2460,7 @@ class TestClient(unittest.TestCase):
self.assertEqual(len(client.feature_flags), 1)
self.assertEqual(client.feature_flags[0]["key"], "beta-feature")
@mock.patch("hanzoanalytics.client.remote_config")
@mock.patch("hanzo_insights.client.remote_config")
def test_get_remote_config_payload_works_without_poller(self, patch_remote_config):
"""Test that get_remote_config_payload works without local evaluation enabled"""
patch_remote_config.return_value = {"test": "payload"}
@@ -2572,7 +2572,7 @@ class TestClient(unittest.TestCase):
client._parse_send_feature_flags(None)
self.assertIn("Invalid type for send_feature_flags", str(cm.exception))
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_capture_with_send_feature_flags_flag_keys_filter(self, patch_flags):
"""Test that SendFeatureFlagsOptions with flag_keys_filter only evaluates specified flags"""
# When flag_keys_to_evaluate is provided, the API should only return the requested flags
@@ -2583,7 +2583,7 @@ class TestClient(unittest.TestCase):
}
}
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(
FAKE_TEST_API_KEY,
on_error=self.set_fail,
@@ -2619,7 +2619,7 @@ class TestClient(unittest.TestCase):
# flag2 should not be included since it wasn't requested
self.assertNotIn("$feature/flag2", msg["properties"])
@mock.patch("hanzoanalytics.client.batch_post")
@mock.patch("hanzo_insights.client.batch_post")
def test_get_feature_flag_result_with_empty_string_payload(self, patch_batch_post):
"""Test that get_feature_flag_result returns a FeatureFlagResult when payload is empty string"""
client = Client(
@@ -2670,7 +2670,7 @@ class TestClient(unittest.TestCase):
self.assertEqual(result.get_value(), "empty-variant")
self.assertEqual(result.payload, "") # Should be empty string, not None
@mock.patch("hanzoanalytics.client.batch_post")
@mock.patch("hanzo_insights.client.batch_post")
def test_get_all_flags_and_payloads_with_empty_string(self, patch_batch_post):
"""Test that get_all_flags_and_payloads includes flags with empty string payloads"""
client = Client(
@@ -2727,7 +2727,7 @@ class TestClient(unittest.TestCase):
)
def test_context_tags_added(self):
with mock.patch("hanzoanalytics.client.batch_post") as mock_post:
with mock.patch("hanzo_insights.client.batch_post") as mock_post:
client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, sync_mode=True)
with new_context():
@@ -2739,7 +2739,7 @@ class TestClient(unittest.TestCase):
self.assertEqual(msg["properties"]["$context_tags"], ["random_tag"])
@mock.patch(
"hanzoanalytics.client.Client._enqueue", side_effect=Exception("Unexpected error")
"hanzo_insights.client.Client._enqueue", side_effect=Exception("Unexpected error")
)
def test_methods_handle_exceptions(self, mock_enqueue):
"""Test that all decorated methods handle exceptions gracefully."""
@@ -2760,7 +2760,7 @@ class TestClient(unittest.TestCase):
self.assertEqual(result, None)
@mock.patch(
"hanzoanalytics.client.Client._enqueue", side_effect=Exception("Expected error")
"hanzo_insights.client.Client._enqueue", side_effect=Exception("Expected error")
)
def test_debug_flag_re_raises_exceptions(self, mock_enqueue):
"""Test that methods re-raise exceptions when debug=True."""
@@ -11,9 +11,9 @@ try:
except ImportError:
from Queue import Queue
from hanzoanalytics.consumer import MAX_MSG_SIZE, Consumer
from hanzoanalytics.request import APIError
from hanzoanalytics.test.test_utils import TEST_API_KEY
from hanzo_insights.consumer import MAX_MSG_SIZE, Consumer
from hanzo_insights.request import APIError
from hanzo_insights.test.test_utils import TEST_API_KEY
def _track_event(event_name: str = "python event") -> dict[str, str]:
@@ -60,7 +60,7 @@ class TestConsumer(unittest.TestCase):
q = Queue()
flush_interval = 0.3
consumer = Consumer(q, TEST_API_KEY, flush_at=10, flush_interval=flush_interval)
with mock.patch("hanzoanalytics.consumer.batch_post") as mock_post:
with mock.patch("hanzo_insights.consumer.batch_post") as mock_post:
consumer.start()
for i in range(3):
q.put(_track_event("python event %d" % i))
@@ -76,7 +76,7 @@ class TestConsumer(unittest.TestCase):
consumer = Consumer(
q, TEST_API_KEY, flush_at=flush_at, flush_interval=flush_interval
)
with mock.patch("hanzoanalytics.consumer.batch_post") as mock_post:
with mock.patch("hanzo_insights.consumer.batch_post") as mock_post:
consumer.start()
for i in range(flush_at * 2):
q.put(_track_event("python event %d" % i))
@@ -99,7 +99,7 @@ class TestConsumer(unittest.TestCase):
consumer = Consumer(None, TEST_API_KEY, retries=retries)
with mock.patch(
"hanzoanalytics.consumer.batch_post", mock.Mock(side_effect=mock_post)
"hanzo_insights.consumer.batch_post", mock.Mock(side_effect=mock_post)
):
if exception_count <= retries:
consumer.request([_track_event()])
@@ -159,7 +159,7 @@ class TestConsumer(unittest.TestCase):
return res
with mock.patch(
"hanzoanalytics.request._session.post", side_effect=mock_post_fn
"hanzo_insights.request._session.post", side_effect=mock_post_fn
) as mock_post:
consumer.start()
for _ in range(0, n_msgs + 2):
@@ -178,8 +178,8 @@ class TestConsumer(unittest.TestCase):
consumer = Consumer(None, TEST_API_KEY, retries=3)
with (
mock.patch("hanzoanalytics.consumer.batch_post", side_effect=mock_post),
mock.patch("hanzoanalytics.consumer.time.sleep") as mock_sleep,
mock.patch("hanzo_insights.consumer.batch_post", side_effect=mock_post),
mock.patch("hanzo_insights.consumer.time.sleep") as mock_sleep,
):
consumer.request([_track_event()])
mock_sleep.assert_called_once_with(5.0)
@@ -195,8 +195,8 @@ class TestConsumer(unittest.TestCase):
consumer = Consumer(None, TEST_API_KEY, retries=3)
with (
mock.patch("hanzoanalytics.consumer.batch_post", side_effect=mock_post),
mock.patch("hanzoanalytics.consumer.time.sleep") as mock_sleep,
mock.patch("hanzo_insights.consumer.batch_post", side_effect=mock_post),
mock.patch("hanzo_insights.consumer.time.sleep") as mock_sleep,
):
consumer.request([_track_event()])
self.assertEqual(
@@ -218,8 +218,8 @@ class TestConsumer(unittest.TestCase):
consumer = Consumer(None, TEST_API_KEY, retries=3)
with (
mock.patch("hanzoanalytics.consumer.batch_post", side_effect=mock_post),
mock.patch("hanzoanalytics.consumer.time.sleep"),
mock.patch("hanzo_insights.consumer.batch_post", side_effect=mock_post),
mock.patch("hanzo_insights.consumer.time.sleep"),
):
consumer.request([_track_event()])
self.assertEqual(call_count[0], 2)
@@ -1,7 +1,7 @@
import unittest
from unittest.mock import patch
from hanzoanalytics.contexts import (
from hanzo_insights.contexts import (
get_tags,
new_context,
scoped,
@@ -66,7 +66,7 @@ class TestContexts(unittest.TestCase):
# Back to level 1
assert get_tags() == {"level1": "value1"}
@patch("hanzoanalytics.capture_exception")
@patch("hanzo_insights.capture_exception")
def test_scoped_decorator_success(self, mock_capture):
@scoped()
def successful_function(x, y):
@@ -85,7 +85,7 @@ class TestContexts(unittest.TestCase):
# Context should be cleared after function execution
assert get_tags() == {}
@patch("hanzoanalytics.capture_exception")
@patch("hanzo_insights.capture_exception")
def test_scoped_decorator_exception(self, mock_capture):
test_exception = ValueError("Test exception")
@@ -111,7 +111,7 @@ class TestContexts(unittest.TestCase):
# Context should be cleared after function execution
assert get_tags() == {}
@patch("hanzoanalytics.capture_exception")
@patch("hanzo_insights.capture_exception")
def test_new_context_exception_handling(self, mock_capture):
test_exception = RuntimeError("Context exception")
@@ -11,7 +11,7 @@ def test_excepthook(tmpdir):
dedent(
"""
from posthog import Posthog
posthog = Posthog('phc_x', host='https://eu.i.hanzoanalytics.com', enable_exception_autocapture=True, debug=True, on_error=lambda e, batch: print('error handling batch: ', e, batch))
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))
# frame_value = "LOL"
@@ -47,7 +47,7 @@ def test_code_variables_capture(tmpdir):
posthog = Posthog(
'phc_x',
host='https://eu.i.hanzoanalytics.com',
host='https://eu.i.hanzo_insights.com',
debug=True,
enable_exception_autocapture=True,
capture_exception_code_variables=True,
@@ -165,7 +165,7 @@ def test_code_variables_context_override(tmpdir):
posthog_client = Posthog(
'phc_x',
host='https://eu.i.hanzoanalytics.com',
host='https://eu.i.hanzo_insights.com',
debug=True,
enable_exception_autocapture=True,
capture_exception_code_variables=False,
@@ -178,10 +178,10 @@ def test_code_variables_context_override(tmpdir):
1/0
with hanzoanalytics.new_context(client=posthog_client):
hanzoanalytics.set_capture_exception_code_variables_context(True)
hanzoanalytics.set_code_variables_mask_patterns_context([r"(?i).*bank.*"])
hanzoanalytics.set_code_variables_ignore_patterns_context([])
with hanzo_insights.new_context(client=posthog_client):
hanzo_insights.set_capture_exception_code_variables_context(True)
hanzo_insights.set_code_variables_mask_patterns_context([r"(?i).*bank.*"])
hanzo_insights.set_code_variables_ignore_patterns_context([])
process_data()
"""
@@ -209,7 +209,7 @@ def test_code_variables_size_limiter(tmpdir):
posthog = Posthog(
'phc_x',
host='https://eu.i.hanzoanalytics.com',
host='https://eu.i.hanzo_insights.com',
debug=True,
enable_exception_autocapture=True,
capture_exception_code_variables=True,
@@ -303,7 +303,7 @@ def test_code_variables_disabled_capture(tmpdir):
posthog = Posthog(
'phc_x',
host='https://eu.i.hanzoanalytics.com',
host='https://eu.i.hanzo_insights.com',
debug=True,
enable_exception_autocapture=True,
capture_exception_code_variables=False,
@@ -345,7 +345,7 @@ def test_code_variables_enabled_then_disabled_in_context(tmpdir):
posthog_client = Posthog(
'phc_x',
host='https://eu.i.hanzoanalytics.com',
host='https://eu.i.hanzo_insights.com',
debug=True,
enable_exception_autocapture=True,
capture_exception_code_variables=True,
@@ -358,8 +358,8 @@ def test_code_variables_enabled_then_disabled_in_context(tmpdir):
1/0
with hanzoanalytics.new_context(client=posthog_client):
hanzoanalytics.set_capture_exception_code_variables_context(False)
with hanzo_insights.new_context(client=posthog_client):
hanzo_insights.set_capture_exception_code_variables_context(False)
process_data()
"""
@@ -396,7 +396,7 @@ def test_code_variables_repr_fallback(tmpdir):
posthog = Posthog(
'phc_x',
host='https://eu.i.hanzoanalytics.com',
host='https://eu.i.hanzo_insights.com',
debug=True,
enable_exception_autocapture=True,
capture_exception_code_variables=True,
@@ -462,7 +462,7 @@ def test_code_variables_too_long_string_value_replaced(tmpdir):
posthog = Posthog(
'phc_x',
host='https://eu.i.hanzoanalytics.com',
host='https://eu.i.hanzo_insights.com',
debug=True,
enable_exception_autocapture=True,
capture_exception_code_variables=True,
@@ -506,7 +506,7 @@ def test_code_variables_too_long_string_in_nested_dict(tmpdir):
posthog = Posthog(
'phc_x',
host='https://eu.i.hanzoanalytics.com',
host='https://eu.i.hanzo_insights.com',
debug=True,
enable_exception_autocapture=True,
capture_exception_code_variables=True,
@@ -547,7 +547,7 @@ def test_code_variables_too_long_string_in_nested_dict(tmpdir):
def test_mask_sensitive_data_too_long_dict_key():
from hanzoanalytics.exception_utils import (
from hanzo_insights.exception_utils import (
CODE_VARIABLES_TOO_LONG_VALUE,
_compile_patterns,
_mask_sensitive_data,
@@ -571,7 +571,7 @@ def test_mask_sensitive_data_too_long_dict_key():
def test_mask_sensitive_data_circular_ref():
from hanzoanalytics.exception_utils import _compile_patterns, _mask_sensitive_data
from hanzo_insights.exception_utils import _compile_patterns, _mask_sensitive_data
compiled_mask = _compile_patterns([r"(?i)password"])
@@ -593,7 +593,7 @@ def test_mask_sensitive_data_circular_ref():
def test_compile_patterns_fast_path_and_regex_fallback():
from hanzoanalytics.exception_utils import _compile_patterns, _pattern_matches
from hanzo_insights.exception_utils import _compile_patterns, _pattern_matches
# Simple case-insensitive patterns should become substrings
simple_only = _compile_patterns([r"(?i)password", r"(?i)token", r"(?i)jwt"])
@@ -642,7 +642,7 @@ def test_compile_patterns_fast_path_and_regex_fallback():
def test_mask_sensitive_data_large_dict_replaced():
from hanzoanalytics.exception_utils import (
from hanzo_insights.exception_utils import (
CODE_VARIABLES_TOO_LONG_VALUE,
_compile_patterns,
_mask_sensitive_data,
@@ -658,7 +658,7 @@ def test_mask_sensitive_data_large_dict_replaced():
def test_mask_sensitive_data_large_list_replaced():
from hanzoanalytics.exception_utils import (
from hanzo_insights.exception_utils import (
CODE_VARIABLES_TOO_LONG_VALUE,
_compile_patterns,
_mask_sensitive_data,
@@ -674,7 +674,7 @@ def test_mask_sensitive_data_large_list_replaced():
def test_mask_sensitive_data_large_tuple_replaced():
from hanzoanalytics.exception_utils import (
from hanzo_insights.exception_utils import (
CODE_VARIABLES_TOO_LONG_VALUE,
_compile_patterns,
_mask_sensitive_data,
@@ -1,6 +1,6 @@
import unittest
from hanzoanalytics.types import FeatureFlag, FlagMetadata, FlagReason, LegacyFlagMetadata
from hanzo_insights.types import FeatureFlag, FlagMetadata, FlagReason, LegacyFlagMetadata
class TestFeatureFlag(unittest.TestCase):
@@ -2,9 +2,9 @@ import unittest
import mock
from hanzoanalytics.client import Client
from hanzoanalytics.test.test_utils import FAKE_TEST_API_KEY
from hanzoanalytics.types import (
from hanzo_insights.client import Client
from hanzo_insights.test.test_utils import FAKE_TEST_API_KEY
from hanzo_insights.types import (
FeatureFlag,
FeatureFlagError,
FeatureFlagResult,
@@ -328,7 +328,7 @@ class TestGetFeatureFlagResult(unittest.TestCase):
disable_geoip=None,
)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
@mock.patch.object(Client, "capture")
def test_get_feature_flag_result_boolean_decide(self, patch_capture, patch_flags):
patch_flags.return_value = {
@@ -375,7 +375,7 @@ class TestGetFeatureFlagResult(unittest.TestCase):
captured_properties = patch_capture.call_args[1]["properties"]
self.assertNotIn("$feature_flag_error", captured_properties)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
@mock.patch.object(Client, "capture")
def test_get_feature_flag_result_variant_decide(self, patch_capture, patch_flags):
patch_flags.return_value = {
@@ -421,7 +421,7 @@ class TestGetFeatureFlagResult(unittest.TestCase):
captured_properties = patch_capture.call_args[1]["properties"]
self.assertNotIn("$feature_flag_error", captured_properties)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
@mock.patch.object(Client, "capture")
def test_get_feature_flag_result_unknown_flag(self, patch_capture, patch_flags):
patch_flags.return_value = {
@@ -461,7 +461,7 @@ class TestGetFeatureFlagResult(unittest.TestCase):
disable_geoip=None,
)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
@mock.patch.object(Client, "capture")
def test_get_feature_flag_result_with_errors_while_computing_flags(
self, patch_capture, patch_flags
@@ -507,7 +507,7 @@ class TestGetFeatureFlagResult(unittest.TestCase):
disable_geoip=None,
)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
@mock.patch.object(Client, "capture")
def test_get_feature_flag_result_flag_not_in_response(
self, patch_capture, patch_flags
@@ -549,7 +549,7 @@ class TestGetFeatureFlagResult(unittest.TestCase):
disable_geoip=None,
)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
@mock.patch.object(Client, "capture")
def test_get_feature_flag_result_errors_computing_and_flag_missing(
self, patch_capture, patch_flags
@@ -585,7 +585,7 @@ class TestGetFeatureFlagResult(unittest.TestCase):
disable_geoip=None,
)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
@mock.patch.object(Client, "capture")
def test_get_feature_flag_result_unknown_error(self, patch_capture, patch_flags):
"""Test that unexpected exceptions are captured as unknown_error."""
@@ -608,11 +608,11 @@ class TestGetFeatureFlagResult(unittest.TestCase):
disable_geoip=None,
)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
@mock.patch.object(Client, "capture")
def test_get_feature_flag_result_timeout_error(self, patch_capture, patch_flags):
"""Test that timeout errors are captured specifically."""
from hanzoanalytics.request import RequestsTimeout
from hanzo_insights.request import RequestsTimeout
patch_flags.side_effect = RequestsTimeout("Request timed out")
@@ -633,11 +633,11 @@ class TestGetFeatureFlagResult(unittest.TestCase):
disable_geoip=None,
)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
@mock.patch.object(Client, "capture")
def test_get_feature_flag_result_connection_error(self, patch_capture, patch_flags):
"""Test that connection errors are captured specifically."""
from hanzoanalytics.request import RequestsConnectionError
from hanzo_insights.request import RequestsConnectionError
patch_flags.side_effect = RequestsConnectionError("Connection refused")
@@ -658,11 +658,11 @@ class TestGetFeatureFlagResult(unittest.TestCase):
disable_geoip=None,
)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
@mock.patch.object(Client, "capture")
def test_get_feature_flag_result_api_error(self, patch_capture, patch_flags):
"""Test that API errors include the status code."""
from hanzoanalytics.request import APIError
from hanzo_insights.request import APIError
patch_flags.side_effect = APIError(500, "Internal server error")
@@ -683,11 +683,11 @@ class TestGetFeatureFlagResult(unittest.TestCase):
disable_geoip=None,
)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
@mock.patch.object(Client, "capture")
def test_get_feature_flag_result_quota_limited(self, patch_capture, patch_flags):
"""Test that quota limit errors are captured specifically."""
from hanzoanalytics.request import QuotaLimitError
from hanzo_insights.request import QuotaLimitError
patch_flags.side_effect = QuotaLimitError(429, "Rate limit exceeded")
@@ -741,11 +741,11 @@ class TestFeatureFlagErrorWithStaleCacheFallback(unittest.TestCase):
flag_definition_version=self.client.flag_definition_version,
)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
@mock.patch.object(Client, "capture")
def test_timeout_error_returns_stale_cached_value(self, patch_capture, patch_flags):
"""Test that timeout errors return stale cached value when available."""
from hanzoanalytics.request import RequestsTimeout
from hanzo_insights.request import RequestsTimeout
# Pre-populate cache with a flag result
cached_result = FeatureFlagResult.from_value_and_payload(
@@ -779,13 +779,13 @@ class TestFeatureFlagErrorWithStaleCacheFallback(unittest.TestCase):
disable_geoip=None,
)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
@mock.patch.object(Client, "capture")
def test_connection_error_returns_stale_cached_value(
self, patch_capture, patch_flags
):
"""Test that connection errors return stale cached value when available."""
from hanzoanalytics.request import RequestsConnectionError
from hanzo_insights.request import RequestsConnectionError
# Pre-populate cache with a boolean flag result
cached_result = FeatureFlagResult.from_value_and_payload("my-flag", True, None)
@@ -816,11 +816,11 @@ class TestFeatureFlagErrorWithStaleCacheFallback(unittest.TestCase):
disable_geoip=None,
)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
@mock.patch.object(Client, "capture")
def test_api_error_returns_stale_cached_value(self, patch_capture, patch_flags):
"""Test that API errors return stale cached value when available."""
from hanzoanalytics.request import APIError
from hanzo_insights.request import APIError
# Pre-populate cache
cached_result = FeatureFlagResult.from_value_and_payload(
@@ -852,11 +852,11 @@ class TestFeatureFlagErrorWithStaleCacheFallback(unittest.TestCase):
disable_geoip=None,
)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
@mock.patch.object(Client, "capture")
def test_error_without_cache_returns_none(self, patch_capture, patch_flags):
"""Test that errors return None when no stale cache is available."""
from hanzoanalytics.request import RequestsTimeout
from hanzo_insights.request import RequestsTimeout
# Do NOT populate cache - no fallback available
@@ -5,14 +5,14 @@ import mock
from dateutil import parser, tz
from freezegun import freeze_time
from hanzoanalytics.client import Client
from hanzoanalytics.feature_flags import (
from hanzo_insights.client import Client
from hanzo_insights.feature_flags import (
InconclusiveMatchError,
match_property,
relative_date_parse_for_feature_flag_matching,
)
from hanzoanalytics.request import APIError, GetResponse
from hanzoanalytics.test.test_utils import FAKE_TEST_API_KEY
from hanzo_insights.request import APIError, GetResponse
from hanzo_insights.test.test_utils import FAKE_TEST_API_KEY
class TestLocalEvaluation(unittest.TestCase):
@@ -35,7 +35,7 @@ class TestLocalEvaluation(unittest.TestCase):
self.failed = False
self.client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail)
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.get")
def test_flag_person_properties(self, patch_get):
self.client.feature_flags = [
{
@@ -137,8 +137,8 @@ class TestLocalEvaluation(unittest.TestCase):
)
)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.flags")
@mock.patch("hanzo_insights.client.get")
def test_flag_group_properties(self, patch_get, patch_flags):
self.client.feature_flags = [
{
@@ -254,8 +254,8 @@ class TestLocalEvaluation(unittest.TestCase):
group_properties={},
)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.flags")
@mock.patch("hanzo_insights.client.get")
def test_flag_with_complex_definition(self, patch_get, patch_flags):
patch_flags.return_value = {
"featureFlags": {"complex-flag": "decide-fallback-value"}
@@ -384,8 +384,8 @@ class TestLocalEvaluation(unittest.TestCase):
)
self.assertEqual(patch_flags.call_count, 0)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.flags")
@mock.patch("hanzo_insights.client.get")
def test_feature_flags_fallback_to_flags(self, patch_get, patch_flags):
patch_flags.return_value = {
"featureFlags": {"beta-feature": "alakazam", "beta-feature2": "alakazam2"}
@@ -450,8 +450,8 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertEqual(feature_flag_match, "alakazam2")
self.assertEqual(patch_flags.call_count, 2)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.flags")
@mock.patch("hanzo_insights.client.get")
def test_feature_flags_dont_fallback_to_flags_when_only_local_evaluation_is_true(
self, patch_get, patch_flags
):
@@ -534,8 +534,8 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertEqual(patch_flags.call_count, 0)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.flags")
@mock.patch("hanzo_insights.client.get")
def test_feature_flag_never_returns_undefined_during_regular_evaluation(
self, patch_get, patch_flags
):
@@ -569,8 +569,8 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertFalse(client.feature_enabled("beta-feature2", "some-distinct-id"))
self.assertEqual(patch_flags.call_count, 2)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.flags")
@mock.patch("hanzo_insights.client.get")
def test_feature_flag_return_none_when_decide_errors_out(
self, patch_get, patch_flags
):
@@ -585,7 +585,7 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertIsNone(client.feature_enabled("beta-feature2", "some-distinct-id"))
self.assertEqual(patch_flags.call_count, 2)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_experience_continuity_flag_not_evaluated_locally(self, patch_flags):
patch_flags.return_value = {
"featureFlags": {"beta-feature": "decide-fallback-value"}
@@ -617,7 +617,7 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertEqual(patch_flags.call_count, 1)
@mock.patch.object(Client, "capture")
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_get_all_flags_with_fallback(self, patch_flags, patch_capture):
patch_flags.return_value = {
"featureFlags": {
@@ -685,7 +685,7 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertEqual(patch_capture.call_count, 0)
@mock.patch.object(Client, "capture")
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_get_all_flags_and_payloads_with_fallback(self, patch_flags, patch_capture):
patch_flags.return_value = {
"featureFlags": {"beta-feature": "variant-1", "beta-feature2": "variant-2"},
@@ -758,7 +758,7 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertEqual(patch_capture.call_count, 0)
@mock.patch.object(Client, "capture")
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_get_all_flags_with_fallback_empty_local_flags(
self, patch_flags, patch_capture
):
@@ -776,7 +776,7 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertEqual(patch_capture.call_count, 0)
@mock.patch.object(Client, "capture")
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_get_all_flags_and_payloads_with_fallback_empty_local_flags(
self, patch_flags, patch_capture
):
@@ -795,7 +795,7 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertEqual(patch_capture.call_count, 0)
@mock.patch.object(Client, "capture")
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_get_all_flags_with_no_fallback(self, patch_flags, patch_capture):
patch_flags.return_value = {
"featureFlags": {"beta-feature": "variant-1", "beta-feature2": "variant-2"}
@@ -841,7 +841,7 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertEqual(patch_capture.call_count, 0)
@mock.patch.object(Client, "capture")
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_get_all_flags_and_payloads_with_no_fallback(
self, patch_flags, patch_capture
):
@@ -894,7 +894,7 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertEqual(patch_capture.call_count, 0)
@mock.patch.object(Client, "capture")
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_get_all_flags_with_fallback_but_only_local_evaluation_set(
self, patch_flags, patch_capture
):
@@ -956,7 +956,7 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertEqual(patch_capture.call_count, 0)
@mock.patch.object(Client, "capture")
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_get_all_flags_and_payloads_with_fallback_but_only_local_evaluation_set(
self, patch_flags, patch_capture
):
@@ -1033,7 +1033,7 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertEqual(patch_capture.call_count, 0)
@mock.patch.object(Client, "capture")
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_compute_inactive_flags_locally(self, patch_flags, patch_capture):
client = self.client
client.feature_flags = [
@@ -1115,8 +1115,8 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertEqual(patch_flags.call_count, 0)
self.assertEqual(patch_capture.call_count, 0)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.flags")
@mock.patch("hanzo_insights.client.get")
def test_feature_flags_local_evaluation_None_values(self, patch_get, patch_flags):
client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY)
client.feature_flags = [
@@ -1190,8 +1190,8 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertEqual(feature_flag_match, True)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.flags")
@mock.patch("hanzo_insights.client.get")
def test_feature_flags_local_evaluation_for_cohorts(self, patch_get, patch_flags):
client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY)
client.feature_flags = [
@@ -1276,8 +1276,8 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertEqual(patch_flags.call_count, 0)
self.assertEqual(patch_get.call_count, 0)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.flags")
@mock.patch("hanzo_insights.client.get")
def test_feature_flags_local_evaluation_for_negated_cohorts(
self, patch_get, patch_flags
):
@@ -1376,9 +1376,9 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertEqual(patch_flags.call_count, 0)
self.assertEqual(patch_get.call_count, 0)
@mock.patch("hanzoanalytics.feature_flags.log")
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.feature_flags.log")
@mock.patch("hanzo_insights.client.flags")
@mock.patch("hanzo_insights.client.get")
def test_feature_flags_with_flag_dependencies(
self, patch_get, patch_flags, mock_log
):
@@ -1439,8 +1439,8 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertEqual(patch_flags.call_count, 2) # Called twice now
self.assertEqual(patch_get.call_count, 0)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.flags")
@mock.patch("hanzo_insights.client.get")
def test_flag_dependencies_simple_chain(self, patch_get, patch_flags):
"""Test basic flag dependency: flag-b depends on flag-a"""
client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY)
@@ -1506,8 +1506,8 @@ class TestLocalEvaluation(unittest.TestCase):
)
self.assertEqual(result, False)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.flags")
@mock.patch("hanzo_insights.client.get")
def test_flag_dependencies_circular_dependency(self, patch_get, patch_flags):
"""Test circular dependency handling: flag-a depends on flag-b, flag-b depends on flag-a"""
# Mock remote flags call to return empty for these flags (fallback returns None)
@@ -1568,8 +1568,8 @@ class TestLocalEvaluation(unittest.TestCase):
result_b = client.get_feature_flag("flag-b", "test-user")
self.assertIsNone(result_b)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.flags")
@mock.patch("hanzo_insights.client.get")
def test_flag_dependencies_missing_flag(self, patch_get, patch_flags):
"""Test handling of missing flag dependency"""
# Mock remote flags call to return empty for this flag (fallback returns None)
@@ -1605,8 +1605,8 @@ class TestLocalEvaluation(unittest.TestCase):
result = client.get_feature_flag("flag-a", "test-user")
self.assertIsNone(result)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.flags")
@mock.patch("hanzo_insights.client.get")
def test_flag_dependencies_complex_chain(self, patch_get, patch_flags):
"""Test complex dependency chain: flag-d -> flag-c -> [flag-a, flag-b]"""
client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY)
@@ -1701,8 +1701,8 @@ class TestLocalEvaluation(unittest.TestCase):
result = client.get_feature_flag("flag-d", "test-user")
self.assertEqual(result, False)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.flags")
@mock.patch("hanzo_insights.client.get")
def test_flag_dependencies_mixed_conditions(self, patch_get, patch_flags):
"""Test flag dependency mixed with other property conditions"""
client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY)
@@ -1776,8 +1776,8 @@ class TestLocalEvaluation(unittest.TestCase):
)
self.assertEqual(result, False)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.flags")
@mock.patch("hanzo_insights.client.get")
def test_flag_dependencies_malformed_chain(self, patch_get, patch_flags):
"""Test handling of malformed dependency chains"""
# Mock remote flags call to return empty for this flag (fallback returns None)
@@ -1829,7 +1829,7 @@ class TestLocalEvaluation(unittest.TestCase):
def test_flag_dependencies_without_context_raises_inconclusive(self):
"""Test that missing flags_by_key raises InconclusiveMatchError"""
from hanzoanalytics.feature_flags import (
from hanzo_insights.feature_flags import (
evaluate_flag_dependency,
InconclusiveMatchError,
)
@@ -1856,8 +1856,8 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertIn("Cannot evaluate flag dependency", str(cm.exception))
self.assertIn("some-flag", str(cm.exception))
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.flags")
@mock.patch("hanzo_insights.client.get")
def test_multi_level_multivariate_dependency_chain(self, patch_get, patch_flags):
"""Test multi-level multivariate dependency chain: dependent-flag -> intermediate-flag -> leaf-flag"""
client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY)
@@ -2093,7 +2093,7 @@ class TestLocalEvaluation(unittest.TestCase):
def test_matches_dependency_value(self):
"""Test the matches_dependency_value function logic"""
from hanzoanalytics.feature_flags import matches_dependency_value
from hanzo_insights.feature_flags import matches_dependency_value
# String variant matches string exactly (case-sensitive)
self.assertTrue(matches_dependency_value("control", "control"))
@@ -2121,8 +2121,8 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertFalse(matches_dependency_value(123, "control"))
self.assertFalse(matches_dependency_value("control", True))
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.flags")
@mock.patch("hanzo_insights.client.get")
def test_production_style_multivariate_dependency_chain(
self, patch_get, patch_flags
):
@@ -2366,8 +2366,8 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertEqual(unknown_intermediate_result, False) # Dependency not satisfied
self.assertEqual(unknown_root_result, False) # Chain broken
@mock.patch("hanzoanalytics.client.Poller")
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.Poller")
@mock.patch("hanzo_insights.client.get")
def test_load_feature_flags(self, patch_get, patch_poll):
patch_get.return_value = GetResponse(
data={
@@ -2403,8 +2403,8 @@ class TestLocalEvaluation(unittest.TestCase):
# Verify ETag is stored
self.assertEqual(client._flags_etag, '"abc123"')
@mock.patch("hanzoanalytics.client.Poller")
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.Poller")
@mock.patch("hanzo_insights.client.get")
def test_load_feature_flags_sends_etag_on_subsequent_requests(
self, patch_get, patch_poll
):
@@ -2431,8 +2431,8 @@ class TestLocalEvaluation(unittest.TestCase):
second_call_kwargs = patch_get.call_args_list[1][1]
self.assertEqual(second_call_kwargs.get("etag"), '"initial-etag"')
@mock.patch("hanzoanalytics.client.Poller")
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.Poller")
@mock.patch("hanzo_insights.client.get")
def test_load_feature_flags_304_not_modified(self, patch_get, patch_poll):
"""Test that 304 Not Modified responses skip flag processing"""
# First response with flags
@@ -2468,8 +2468,8 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertEqual(client.feature_flags[0]["key"], "beta-feature")
self.assertEqual(client.group_type_mapping, {"0": "company"})
@mock.patch("hanzoanalytics.client.Poller")
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.Poller")
@mock.patch("hanzo_insights.client.get")
def test_load_feature_flags_etag_updated_on_new_response(
self, patch_get, patch_poll
):
@@ -2501,8 +2501,8 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertEqual(client._flags_etag, '"etag-v2"')
self.assertEqual(client.feature_flags[0]["key"], "flag-v2")
@mock.patch("hanzoanalytics.client.Poller")
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.Poller")
@mock.patch("hanzo_insights.client.get")
def test_load_feature_flags_clears_etag_when_server_stops_sending(
self, patch_get, patch_poll
):
@@ -2534,23 +2534,23 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertIsNone(client._flags_etag)
self.assertEqual(client.feature_flags[0]["key"], "flag-v2")
@mock.patch("hanzoanalytics.client.Poller")
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.Poller")
@mock.patch("hanzo_insights.client.get")
def test_load_feature_flags_wrong_key(self, patch_get, _patch_poll):
patch_get.side_effect = APIError(401, "Unauthorized")
client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY)
with self.assertLogs("hanzoanalytics", level="ERROR") as logs:
with self.assertLogs("hanzo_insights", level="ERROR") as logs:
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://hanzoanalytics.com/docs/api/overview",
"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",
)
client.debug = True
self.assertRaises(APIError, client.load_feature_flags)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.flags")
@mock.patch("hanzo_insights.client.get")
def test_feature_enabled_simple(self, patch_get, patch_flags):
client = Client(FAKE_TEST_API_KEY)
client.feature_flags = [
@@ -2573,8 +2573,8 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertTrue(client.feature_enabled("beta-feature", "distinct_id"))
self.assertEqual(patch_flags.call_count, 0)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.flags")
@mock.patch("hanzo_insights.client.get")
def test_feature_enabled_simple_is_false(self, patch_get, patch_flags):
client = Client(FAKE_TEST_API_KEY)
client.feature_flags = [
@@ -2597,8 +2597,8 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertFalse(client.feature_enabled("beta-feature", "distinct_id"))
self.assertEqual(patch_flags.call_count, 0)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.flags")
@mock.patch("hanzo_insights.client.get")
def test_feature_enabled_simple_is_true_when_rollout_is_undefined(
self, patch_get, patch_flags
):
@@ -2623,7 +2623,7 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertTrue(client.feature_enabled("beta-feature", "distinct_id"))
self.assertEqual(patch_flags.call_count, 0)
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.get")
def test_feature_enabled_simple_with_project_api_key(self, patch_get):
client = Client(project_api_key=FAKE_TEST_API_KEY, on_error=self.set_fail)
client.feature_flags = [
@@ -2645,7 +2645,7 @@ class TestLocalEvaluation(unittest.TestCase):
]
self.assertTrue(client.feature_enabled("beta-feature", "distinct_id"))
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_feature_enabled_request_multi_variate(self, patch_flags):
patch_flags.return_value = {"featureFlags": {"beta-feature": "variant-1"}}
client = Client(FAKE_TEST_API_KEY, personal_api_key="test")
@@ -2670,7 +2670,7 @@ class TestLocalEvaluation(unittest.TestCase):
# decide not called because this can be evaluated locally
self.assertEqual(patch_flags.call_count, 0)
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.get")
def test_feature_enabled_simple_without_rollout_percentage(self, patch_get):
client = Client(FAKE_TEST_API_KEY)
client.feature_flags = [
@@ -2690,7 +2690,7 @@ class TestLocalEvaluation(unittest.TestCase):
]
self.assertTrue(client.feature_enabled("beta-feature", "distinct_id"))
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_get_feature_flag(self, patch_flags):
patch_flags.return_value = {"featureFlags": {"beta-feature": "variant-1"}}
client = Client(FAKE_TEST_API_KEY, personal_api_key="test")
@@ -2723,8 +2723,8 @@ class TestLocalEvaluation(unittest.TestCase):
# decide not called because this can be evaluated locally
self.assertEqual(patch_flags.call_count, 0)
@mock.patch("hanzoanalytics.client.Poller")
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.Poller")
@mock.patch("hanzo_insights.client.flags")
def test_feature_enabled_doesnt_exist(self, patch_flags, patch_poll):
client = Client(FAKE_TEST_API_KEY)
client.feature_flags = []
@@ -2735,8 +2735,8 @@ class TestLocalEvaluation(unittest.TestCase):
patch_flags.side_effect = APIError(401, "decide error")
self.assertIsNone(client.feature_enabled("doesnt-exist", "distinct_id"))
@mock.patch("hanzoanalytics.client.Poller")
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.Poller")
@mock.patch("hanzo_insights.client.flags")
def test_personal_api_key_doesnt_exist(self, patch_flags, patch_poll):
client = Client(FAKE_TEST_API_KEY, personal_api_key="test")
client.feature_flags = []
@@ -2745,8 +2745,8 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertTrue(client.feature_enabled("feature-flag", "distinct_id"))
@mock.patch("hanzoanalytics.client.Poller")
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.Poller")
@mock.patch("hanzo_insights.client.get")
def test_load_feature_flags_error(self, patch_get, patch_poll):
def raise_effect():
raise Exception("http exception")
@@ -2757,7 +2757,7 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertFalse(client.feature_enabled("doesnt-exist", "distinct_id"))
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_get_feature_flag_with_variant_overrides(self, patch_flags):
patch_flags.return_value = {"featureFlags": {"beta-feature": "variant-1"}}
client = Client(FAKE_TEST_API_KEY, personal_api_key="test")
@@ -2775,7 +2775,7 @@ class TestLocalEvaluation(unittest.TestCase):
{
"key": "email",
"type": "person",
"value": "test@hanzoanalytics.com",
"value": "test@hanzo_insights.com",
"operator": "exact",
}
],
@@ -2810,7 +2810,7 @@ class TestLocalEvaluation(unittest.TestCase):
client.get_feature_flag(
"beta-feature",
"test_id",
person_properties={"email": "test@hanzoanalytics.com"},
person_properties={"email": "test@hanzo_insights.com"},
),
"second-variant",
)
@@ -2820,7 +2820,7 @@ class TestLocalEvaluation(unittest.TestCase):
# decide not called because this can be evaluated locally
self.assertEqual(patch_flags.call_count, 0)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_flag_with_clashing_variant_overrides(self, patch_flags):
patch_flags.return_value = {"featureFlags": {"beta-feature": "variant-1"}}
client = Client(FAKE_TEST_API_KEY, personal_api_key="test")
@@ -2838,7 +2838,7 @@ class TestLocalEvaluation(unittest.TestCase):
{
"key": "email",
"type": "person",
"value": "test@hanzoanalytics.com",
"value": "test@hanzo_insights.com",
"operator": "exact",
}
],
@@ -2851,7 +2851,7 @@ class TestLocalEvaluation(unittest.TestCase):
{
"key": "email",
"type": "person",
"value": "test@hanzoanalytics.com",
"value": "test@hanzo_insights.com",
"operator": "exact",
}
],
@@ -2886,7 +2886,7 @@ class TestLocalEvaluation(unittest.TestCase):
client.get_feature_flag(
"beta-feature",
"test_id",
person_properties={"email": "test@hanzoanalytics.com"},
person_properties={"email": "test@hanzo_insights.com"},
),
"second-variant",
)
@@ -2894,14 +2894,14 @@ class TestLocalEvaluation(unittest.TestCase):
client.get_feature_flag(
"beta-feature",
"example_id",
person_properties={"email": "test@hanzoanalytics.com"},
person_properties={"email": "test@hanzo_insights.com"},
),
"second-variant",
)
# decide not called because this can be evaluated locally
self.assertEqual(patch_flags.call_count, 0)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_flag_with_invalid_variant_overrides(self, patch_flags):
patch_flags.return_value = {"featureFlags": {"beta-feature": "variant-1"}}
client = Client(FAKE_TEST_API_KEY, personal_api_key="test")
@@ -2919,7 +2919,7 @@ class TestLocalEvaluation(unittest.TestCase):
{
"key": "email",
"type": "person",
"value": "test@hanzoanalytics.com",
"value": "test@hanzo_insights.com",
"operator": "exact",
}
],
@@ -2954,7 +2954,7 @@ class TestLocalEvaluation(unittest.TestCase):
client.get_feature_flag(
"beta-feature",
"test_id",
person_properties={"email": "test@hanzoanalytics.com"},
person_properties={"email": "test@hanzo_insights.com"},
),
"third-variant",
)
@@ -2964,7 +2964,7 @@ class TestLocalEvaluation(unittest.TestCase):
# decide not called because this can be evaluated locally
self.assertEqual(patch_flags.call_count, 0)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_conditions_evaluated_in_order(self, patch_flags):
patch_flags.return_value = {"featureFlags": {"order-test": "server-variant"}}
client = Client(FAKE_TEST_API_KEY, personal_api_key="test")
@@ -3022,7 +3022,7 @@ class TestLocalEvaluation(unittest.TestCase):
# server not called because this can be evaluated locally
self.assertEqual(patch_flags.call_count, 0)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_boolean_feature_flag_payloads_local(self, patch_flags):
basic_flag = {
"id": 1,
@@ -3067,7 +3067,7 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertEqual(patch_flags.call_count, 0)
@mock.patch.object(Client, "capture")
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_boolean_feature_flag_payload_decide(self, patch_flags, patch_capture):
patch_flags.return_value = {
"featureFlags": {"person-flag": True},
@@ -3094,7 +3094,7 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertEqual(patch_capture.call_count, 1)
patch_capture.reset_mock()
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_multivariate_feature_flag_payloads(self, patch_flags):
multivariate_flag = {
"id": 1,
@@ -3109,7 +3109,7 @@ class TestLocalEvaluation(unittest.TestCase):
{
"key": "email",
"type": "person",
"value": "test@hanzoanalytics.com",
"value": "test@hanzo_insights.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@hanzoanalytics.com"},
person_properties={"email": "test@hanzo_insights.com"},
),
{"a": "json"},
)
@@ -3158,7 +3158,7 @@ class TestLocalEvaluation(unittest.TestCase):
"beta-feature",
"test_id",
match_value="third-variant",
person_properties={"email": "test@hanzoanalytics.com"},
person_properties={"email": "test@hanzo_insights.com"},
),
{"a": "json"},
)
@@ -3169,14 +3169,14 @@ class TestLocalEvaluation(unittest.TestCase):
"beta-feature",
"test_id",
match_value="first-variant",
person_properties={"email": "test@hanzoanalytics.com"},
person_properties={"email": "test@hanzo_insights.com"},
),
"some-payload",
)
self.assertEqual(patch_flags.call_count, 0)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.flags")
@mock.patch("hanzo_insights.client.get")
def test_fallback_to_api_when_flag_has_static_cohort_in_multi_condition(
self, patch_get, patch_flags
):
@@ -3244,7 +3244,7 @@ class TestLocalEvaluation(unittest.TestCase):
# Verify API was called (fallback occurred)
self.assertEqual(patch_flags.call_count, 1)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_device_id_bucketing_uses_device_id_for_hash(self, patch_flags):
"""
When a flag has bucketing_identifier: "device_id", the device_id should be
@@ -3292,7 +3292,7 @@ class TestLocalEvaluation(unittest.TestCase):
match_feature_flag_properties should preserve backward compatibility when
bucketing_value is omitted, while warning about deprecation.
"""
from hanzoanalytics.feature_flags import match_feature_flag_properties
from hanzo_insights.feature_flags import match_feature_flag_properties
flag = {
"id": 1,
@@ -3321,7 +3321,7 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertTrue(result)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_device_id_bucketing_same_device_different_users_same_result(
self, patch_flags
):
@@ -3365,7 +3365,7 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertEqual(patch_flags.call_count, 0)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_device_id_bucketing_fallback_when_device_id_missing(self, patch_flags):
"""
When a flag requires device_id for bucketing but none is provided,
@@ -3398,7 +3398,7 @@ class TestLocalEvaluation(unittest.TestCase):
# API should have been called
self.assertEqual(patch_flags.call_count, 1)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_device_id_bucketing_returns_none_when_only_evaluate_locally_and_no_device_id(
self, patch_flags
):
@@ -3434,7 +3434,7 @@ class TestLocalEvaluation(unittest.TestCase):
# API should NOT have been called
self.assertEqual(patch_flags.call_count, 0)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_default_bucketing_identifier_uses_distinct_id(self, patch_flags):
"""
When bucketing_identifier is not set or is 'distinct_id', should use
@@ -3467,7 +3467,7 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertEqual(result1, result2)
self.assertEqual(patch_flags.call_count, 0)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_device_id_bucketing_with_multivariate_flag(self, patch_flags):
"""
Multivariate flag variant selection should use device_id when
@@ -3512,13 +3512,13 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertEqual(patch_flags.call_count, 0)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_device_id_bucketing_from_context(self, patch_flags):
"""
When device_id is not passed as a parameter but is set in the context,
it should be resolved from context.
"""
from hanzoanalytics.contexts import new_context, set_context_device_id
from hanzo_insights.contexts import new_context, set_context_device_id
client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY)
@@ -3548,7 +3548,7 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertTrue(result)
self.assertEqual(patch_flags.call_count, 0)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_group_flags_ignore_bucketing_identifier(self, patch_flags):
"""
Group flags should continue to use the group identifier for hashing,
@@ -3586,7 +3586,7 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertTrue(result)
self.assertEqual(patch_flags.call_count, 0)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_group_flag_dependency_receives_device_id(self, patch_flags):
"""
Group flag dependency evaluation should receive device_id so dependent
@@ -3645,7 +3645,7 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertTrue(result)
self.assertEqual(patch_flags.call_count, 0)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_group_flag_dependency_ignores_device_id_bucketing_identifier(
self, patch_flags
):
@@ -3706,7 +3706,7 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertTrue(result)
self.assertEqual(patch_flags.call_count, 0)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_get_all_flags_with_device_id_bucketing(self, patch_flags):
"""
get_all_flags_and_payloads should properly handle flags with device_id bucketing.
@@ -3740,7 +3740,7 @@ class TestLocalEvaluation(unittest.TestCase):
self.assertEqual(result["device-flag"], True)
self.assertEqual(patch_flags.call_count, 0)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_get_all_flags_fallback_when_device_id_missing_for_some_flags(
self, patch_flags
):
@@ -4610,7 +4610,7 @@ class TestRelativeDateParsing(unittest.TestCase):
class TestCaptureCalls(unittest.TestCase):
@mock.patch.object(Client, "capture")
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_capture_is_called(self, patch_flags, patch_capture):
patch_flags.return_value = {"featureFlags": {"decide-flag": "decide-value"}}
client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY)
@@ -4725,7 +4725,7 @@ class TestCaptureCalls(unittest.TestCase):
)
@mock.patch.object(Client, "capture")
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_capture_is_called_with_flag_details(self, patch_flags, patch_capture):
patch_flags.return_value = {
"flags": {
@@ -4784,7 +4784,7 @@ class TestCaptureCalls(unittest.TestCase):
)
@mock.patch.object(Client, "capture")
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_capture_is_called_with_flag_details_and_payload(
self, patch_flags, patch_capture
):
@@ -4837,7 +4837,7 @@ class TestCaptureCalls(unittest.TestCase):
disable_geoip=None,
)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_capture_is_called_but_does_not_add_all_flags(self, patch_flags):
patch_flags.return_value = {"featureFlags": {"decide-flag": "decide-value"}}
client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY)
@@ -4889,7 +4889,7 @@ class TestCaptureCalls(unittest.TestCase):
assert "$active_feature_flags" not in msg["properties"]
@mock.patch.object(Client, "capture")
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_get_feature_flag_payload_does_not_send_feature_flag_called_events(
self, patch_flags, patch_capture
):
@@ -4928,7 +4928,7 @@ class TestCaptureCalls(unittest.TestCase):
self.assertIsNotNone(payload)
self.assertEqual(patch_capture.call_count, 0)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_fallback_to_api_in_get_feature_flag_payload_when_flag_has_static_cohort(
self, patch_flags
):
@@ -4983,7 +4983,7 @@ class TestCaptureCalls(unittest.TestCase):
self.assertEqual(patch_flags.call_count, 1)
@mock.patch.object(Client, "capture")
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_disable_geoip_get_flag_capture_call(self, patch_flags, patch_capture):
patch_flags.return_value = {"featureFlags": {"decide-flag": "decide-value"}}
client = Client(
@@ -5027,7 +5027,7 @@ class TestCaptureCalls(unittest.TestCase):
)
@mock.patch.object(Client, "capture")
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_capture_multiple_users_doesnt_out_of_memory(
self, patch_flags, patch_capture
):
@@ -5097,7 +5097,7 @@ class TestConsistency(unittest.TestCase):
self.failed = False
self.client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail)
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.get")
def test_simple_flag_consistency(self, patch_get):
self.client.feature_flags = [
{
@@ -6124,7 +6124,7 @@ class TestConsistency(unittest.TestCase):
else:
self.assertFalse(feature_flag_match)
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.get")
def test_multivariate_flag_consistency(self, patch_get):
self.client.feature_flags = [
{
@@ -7181,7 +7181,7 @@ class TestConsistency(unittest.TestCase):
else:
self.assertFalse(feature_flag_match)
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_feature_flag_case_sensitive(self, mock_decide):
mock_decide.return_value = {
"featureFlags": {}
@@ -7206,7 +7206,7 @@ class TestConsistency(unittest.TestCase):
self.assertFalse(client.feature_enabled("beta-feature", "user1"))
self.assertFalse(client.feature_enabled("BETA-FEATURE", "user1"))
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_feature_flag_payload_case_sensitive(self, mock_decide):
mock_decide.return_value = {
"featureFlags": {"Beta-Feature": True},
@@ -7237,7 +7237,7 @@ class TestConsistency(unittest.TestCase):
self.assertIsNone(client.get_feature_flag_payload("beta-feature", "user1"))
self.assertIsNone(client.get_feature_flag_payload("BETA-FEATURE", "user1"))
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_feature_flag_case_sensitive_consistency(self, mock_decide):
mock_decide.return_value = {
"featureFlags": {"Beta-Feature": True},
@@ -7273,7 +7273,7 @@ class TestConsistency(unittest.TestCase):
for case in test_cases:
self.assertFalse(client.feature_enabled(case, "user1"))
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_get_all_flags_with_flag_keys_to_evaluate(self, mock_flags):
"""Test that get_all_flags with flag_keys_to_evaluate only evaluates specified flags"""
mock_flags.return_value = {
@@ -7305,7 +7305,7 @@ class TestConsistency(unittest.TestCase):
# Check the result
self.assertEqual(result, {"flag1": "value1", "flag2": True})
@mock.patch("hanzoanalytics.client.flags")
@mock.patch("hanzo_insights.client.flags")
def test_get_all_flags_and_payloads_with_flag_keys_to_evaluate(self, mock_flags):
"""Test that get_all_flags_and_payloads with flag_keys_to_evaluate only evaluates specified flags"""
mock_flags.return_value = {
@@ -9,13 +9,13 @@ import unittest
from typing import Optional
from unittest import mock
from hanzoanalytics.client import Client
from hanzoanalytics.flag_definition_cache import (
from hanzo_insights.client import Client
from hanzo_insights.flag_definition_cache import (
FlagDefinitionCacheData,
FlagDefinitionCacheProvider,
)
from hanzoanalytics.request import GetResponse
from hanzoanalytics.test.test_utils import FAKE_TEST_API_KEY
from hanzo_insights.request import GetResponse
from hanzo_insights.test.test_utils import FAKE_TEST_API_KEY
class MockCacheProvider:
@@ -63,8 +63,8 @@ class TestFlagDefinitionCacheProvider(unittest.TestCase):
@classmethod
def setUpClass(cls):
# Prevent real HTTP requests
cls.client_post_patcher = mock.patch("hanzoanalytics.client.batch_post")
cls.consumer_post_patcher = mock.patch("hanzoanalytics.consumer.batch_post")
cls.client_post_patcher = mock.patch("hanzo_insights.client.batch_post")
cls.consumer_post_patcher = mock.patch("hanzo_insights.consumer.batch_post")
cls.client_post_patcher.start()
cls.consumer_post_patcher.start()
@@ -102,7 +102,7 @@ class TestFlagDefinitionCacheProvider(unittest.TestCase):
class TestCacheInitialization(TestFlagDefinitionCacheProvider):
"""Tests for cache initialization behavior."""
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.get")
def test_uses_cached_data_when_should_fetch_returns_false(self, mock_get):
"""When should_fetch returns False and cache has data, use cached data."""
self.cache_provider.should_fetch_return_value = False
@@ -124,7 +124,7 @@ class TestCacheInitialization(TestFlagDefinitionCacheProvider):
client.join()
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.get")
def test_fetches_from_api_when_should_fetch_returns_true(self, mock_get):
"""When should_fetch returns True, fetch from API."""
self.cache_provider.should_fetch_return_value = True
@@ -148,7 +148,7 @@ class TestCacheInitialization(TestFlagDefinitionCacheProvider):
client.join()
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.get")
def test_emergency_fallback_when_cache_empty_and_no_flags(self, mock_get):
"""When should_fetch=False but cache is empty and no flags loaded, fetch anyway."""
self.cache_provider.should_fetch_return_value = False
@@ -169,7 +169,7 @@ class TestCacheInitialization(TestFlagDefinitionCacheProvider):
client.join()
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.get")
def test_preserves_existing_flags_when_cache_returns_none(self, mock_get):
"""When cache returns None but client has flags, preserve existing flags."""
self.cache_provider.should_fetch_return_value = False
@@ -197,7 +197,7 @@ class TestCacheInitialization(TestFlagDefinitionCacheProvider):
class TestFetchCoordination(TestFlagDefinitionCacheProvider):
"""Tests for fetch coordination between workers."""
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.get")
def test_calls_should_fetch_before_each_poll(self, mock_get):
"""should_fetch_flag_definitions is called before each poll cycle."""
self.cache_provider.should_fetch_return_value = True
@@ -218,7 +218,7 @@ class TestFetchCoordination(TestFlagDefinitionCacheProvider):
client.join()
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.get")
def test_does_not_call_on_received_when_fetch_skipped(self, mock_get):
"""on_flag_definitions_received is NOT called when fetch is skipped."""
self.cache_provider.should_fetch_return_value = False
@@ -232,7 +232,7 @@ class TestFetchCoordination(TestFlagDefinitionCacheProvider):
client.join()
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.get")
def test_stores_data_in_cache_after_api_fetch(self, mock_get):
"""on_flag_definitions_received receives the fetched data."""
self.cache_provider.should_fetch_return_value = True
@@ -251,7 +251,7 @@ class TestFetchCoordination(TestFlagDefinitionCacheProvider):
client.join()
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.get")
def test_304_not_modified_does_not_update_cache(self, mock_get):
"""When API returns 304 Not Modified, cache should not be updated."""
self.cache_provider.should_fetch_return_value = True
@@ -293,7 +293,7 @@ class TestFetchCoordination(TestFlagDefinitionCacheProvider):
class TestErrorHandling(TestFlagDefinitionCacheProvider):
"""Tests for error handling in cache provider operations."""
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.get")
def test_should_fetch_error_defaults_to_fetching(self, mock_get):
"""When should_fetch throws an error, default to fetching from API."""
self.cache_provider.should_fetch_error = Exception("Lock acquisition failed")
@@ -313,7 +313,7 @@ class TestErrorHandling(TestFlagDefinitionCacheProvider):
client.join()
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.get")
def test_get_error_falls_back_to_api_fetch(self, mock_get):
"""When get_flag_definitions throws an error, fetch from API."""
self.cache_provider.should_fetch_return_value = False
@@ -331,7 +331,7 @@ class TestErrorHandling(TestFlagDefinitionCacheProvider):
client.join()
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.get")
def test_on_received_error_keeps_flags_in_memory(self, mock_get):
"""When on_flag_definitions_received throws, flags are still in memory."""
self.cache_provider.should_fetch_return_value = True
@@ -350,7 +350,7 @@ class TestErrorHandling(TestFlagDefinitionCacheProvider):
client.join()
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.get")
def test_shutdown_error_is_logged_but_continues(self, mock_get):
"""When shutdown throws an error, it's logged but shutdown continues."""
self.cache_provider.shutdown_error = Exception("Lock release failed")
@@ -372,7 +372,7 @@ class TestErrorHandling(TestFlagDefinitionCacheProvider):
class TestShutdownLifecycle(TestFlagDefinitionCacheProvider):
"""Tests for shutdown lifecycle."""
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.get")
def test_shutdown_calls_cache_provider_shutdown(self, mock_get):
"""Client shutdown calls cache provider shutdown."""
mock_get.return_value = GetResponse(
@@ -387,7 +387,7 @@ class TestShutdownLifecycle(TestFlagDefinitionCacheProvider):
self.assertEqual(self.cache_provider.shutdown_call_count, 1)
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.get")
def test_shutdown_called_even_without_fetching(self, mock_get):
"""Shutdown is called even when cache was used instead of fetching."""
self.cache_provider.should_fetch_return_value = False
@@ -400,7 +400,7 @@ class TestShutdownLifecycle(TestFlagDefinitionCacheProvider):
# Shutdown should still be called
self.assertEqual(self.cache_provider.shutdown_call_count, 1)
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.get")
def test_multiple_join_calls_only_shutdown_once(self, mock_get):
"""Calling join() multiple times should only call cache provider shutdown once."""
mock_get.return_value = GetResponse(
@@ -423,7 +423,7 @@ class TestShutdownLifecycle(TestFlagDefinitionCacheProvider):
class TestBackwardCompatibility(TestFlagDefinitionCacheProvider):
"""Tests for backward compatibility without cache provider."""
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.get")
def test_works_without_cache_provider(self, mock_get):
"""Client works normally without a cache provider configured."""
mock_get.return_value = GetResponse(
@@ -451,7 +451,7 @@ class TestBackwardCompatibility(TestFlagDefinitionCacheProvider):
class TestDataIntegrity(TestFlagDefinitionCacheProvider):
"""Tests for data integrity between cache and client state."""
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.get")
def test_cached_flags_available_for_evaluation(self, mock_get):
"""Flags loaded from cache are available for local evaluation."""
self.cache_provider.should_fetch_return_value = False
@@ -483,7 +483,7 @@ class TestDataIntegrity(TestFlagDefinitionCacheProvider):
client.join()
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.get")
def test_group_type_mapping_loaded_from_cache(self, mock_get):
"""Group type mapping is correctly loaded from cache."""
self.cache_provider.should_fetch_return_value = False
@@ -497,7 +497,7 @@ class TestDataIntegrity(TestFlagDefinitionCacheProvider):
client.join()
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.get")
def test_cohorts_loaded_from_cache(self, mock_get):
"""Cohorts are correctly loaded from cache."""
self.cache_provider.should_fetch_return_value = False
@@ -510,7 +510,7 @@ class TestDataIntegrity(TestFlagDefinitionCacheProvider):
client.join()
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.get")
def test_cache_updated_when_api_returns_new_data(self, mock_get):
"""State transition: cache has old data -> API returns new -> cache updated."""
# Start with old cached data
@@ -556,7 +556,7 @@ class TestDataIntegrity(TestFlagDefinitionCacheProvider):
class TestConcurrency(TestFlagDefinitionCacheProvider):
"""Tests for thread safety and concurrent access."""
@mock.patch("hanzoanalytics.client.get")
@mock.patch("hanzo_insights.client.get")
def test_concurrent_load_feature_flags_is_thread_safe(self, mock_get):
"""Multiple threads calling _load_feature_flags should not cause errors."""
mock_get.return_value = GetResponse(
@@ -19,14 +19,14 @@ class TestModule(unittest.TestCase):
)
def test_track(self):
res = self.hanzoanalytics.capture("python module event", distinct_id="distinct_id")
res = self.hanzo_insights.capture("python module event", distinct_id="distinct_id")
self._assert_enqueue_result(res)
self.hanzoanalytics.flush()
self.hanzo_insights.flush()
def test_alias(self):
res = self.hanzoanalytics.alias("previousId", "distinct_id")
res = self.hanzo_insights.alias("previousId", "distinct_id")
self._assert_enqueue_result(res)
self.hanzoanalytics.flush()
self.hanzo_insights.flush()
def test_flush(self):
self.hanzoanalytics.flush()
self.hanzo_insights.flush()
@@ -6,8 +6,8 @@ import mock
import pytest
import requests
import hanzoanalytics.request as request_module
from hanzoanalytics.request import (
import hanzo_insights.request as request_module
from hanzo_insights.request import (
APIError,
DatetimeSerializer,
GetResponse,
@@ -23,7 +23,7 @@ from hanzoanalytics.request import (
get,
set_socket_options,
)
from hanzoanalytics.test.test_utils import TEST_API_KEY
from hanzo_insights.test.test_utils import TEST_API_KEY
@pytest.mark.parametrize(
@@ -72,12 +72,12 @@ class TestRequests(unittest.TestCase):
def test_invalid_request_error(self):
self.assertRaises(
Exception, batch_post, "testsecret", "https://t.hanzoanalytics.com", False, "[{]"
Exception, batch_post, "testsecret", "https://t.hanzo_insights.com", False, "[{]"
)
def test_invalid_host(self):
self.assertRaises(
Exception, batch_post, "testsecret", "t.hanzoanalytics.com/", batch=[]
Exception, batch_post, "testsecret", "t.hanzo_insights.com/", batch=[]
)
def test_datetime_serialization(self):
@@ -128,7 +128,7 @@ class TestRequests(unittest.TestCase):
}
).encode("utf-8")
with mock.patch("hanzoanalytics.request._session.post", return_value=mock_response):
with mock.patch("hanzo_insights.request._session.post", return_value=mock_response):
with self.assertRaises(QuotaLimitError) as cm:
decide("fake_key", "fake_host")
@@ -146,7 +146,7 @@ class TestRequests(unittest.TestCase):
}
).encode("utf-8")
with mock.patch("hanzoanalytics.request._session.post", return_value=mock_response):
with mock.patch("hanzo_insights.request._session.post", return_value=mock_response):
response = decide("fake_key", "fake_host")
self.assertEqual(response["featureFlags"], {"flag1": True})
@@ -154,7 +154,7 @@ class TestRequests(unittest.TestCase):
class TestGet(unittest.TestCase):
"""Unit tests for the get() function HTTP-level behavior."""
@mock.patch("hanzoanalytics.request._session.get")
@mock.patch("hanzo_insights.request._session.get")
def test_get_returns_data_and_etag(self, mock_get):
"""Test that get() returns GetResponse with data and etag from headers."""
mock_response = requests.Response()
@@ -172,7 +172,7 @@ class TestGet(unittest.TestCase):
self.assertEqual(response.etag, '"abc123"')
self.assertFalse(response.not_modified)
@mock.patch("hanzoanalytics.request._session.get")
@mock.patch("hanzo_insights.request._session.get")
def test_get_sends_if_none_match_header_when_etag_provided(self, mock_get):
"""Test that If-None-Match header is sent when etag parameter is provided."""
mock_response = requests.Response()
@@ -186,7 +186,7 @@ class TestGet(unittest.TestCase):
call_kwargs = mock_get.call_args[1]
self.assertEqual(call_kwargs["headers"]["If-None-Match"], '"previous-etag"')
@mock.patch("hanzoanalytics.request._session.get")
@mock.patch("hanzo_insights.request._session.get")
def test_get_does_not_send_if_none_match_when_no_etag(self, mock_get):
"""Test that If-None-Match header is not sent when no etag provided."""
mock_response = requests.Response()
@@ -199,7 +199,7 @@ class TestGet(unittest.TestCase):
call_kwargs = mock_get.call_args[1]
self.assertNotIn("If-None-Match", call_kwargs["headers"])
@mock.patch("hanzoanalytics.request._session.get")
@mock.patch("hanzo_insights.request._session.get")
def test_get_handles_304_not_modified(self, mock_get):
"""Test that 304 Not Modified response returns not_modified=True with no data."""
mock_response = requests.Response()
@@ -216,7 +216,7 @@ class TestGet(unittest.TestCase):
self.assertEqual(response.etag, '"unchanged-etag"')
self.assertTrue(response.not_modified)
@mock.patch("hanzoanalytics.request._session.get")
@mock.patch("hanzo_insights.request._session.get")
def test_get_304_without_etag_header_uses_request_etag(self, mock_get):
"""Test that 304 response without ETag header falls back to request etag."""
mock_response = requests.Response()
@@ -231,7 +231,7 @@ class TestGet(unittest.TestCase):
self.assertTrue(response.not_modified)
self.assertEqual(response.etag, '"original-etag"')
@mock.patch("hanzoanalytics.request._session.get")
@mock.patch("hanzo_insights.request._session.get")
def test_get_200_without_etag_header(self, mock_get):
"""Test that 200 response without ETag header returns None for etag."""
mock_response = requests.Response()
@@ -246,7 +246,7 @@ class TestGet(unittest.TestCase):
self.assertIsNone(response.etag)
self.assertEqual(response.data, {"flags": []})
@mock.patch("hanzoanalytics.request._session.get")
@mock.patch("hanzo_insights.request._session.get")
def test_get_error_response_raises_api_error(self, mock_get):
"""Test that error responses raise APIError."""
mock_response = requests.Response()
@@ -260,7 +260,7 @@ class TestGet(unittest.TestCase):
self.assertEqual(ctx.exception.status, 401)
self.assertEqual(ctx.exception.message, "Unauthorized")
@mock.patch("hanzoanalytics.request._session.get")
@mock.patch("hanzo_insights.request._session.get")
def test_get_sends_authorization_header(self, mock_get):
"""Test that Authorization header is sent with Bearer token."""
mock_response = requests.Response()
@@ -273,7 +273,7 @@ class TestGet(unittest.TestCase):
call_kwargs = mock_get.call_args[1]
self.assertEqual(call_kwargs["headers"]["Authorization"], "Bearer my-api-key")
@mock.patch("hanzoanalytics.request._session.get")
@mock.patch("hanzo_insights.request._session.get")
def test_get_sends_user_agent_header(self, mock_get):
"""Test that User-Agent header is sent."""
mock_response = requests.Response()
@@ -289,7 +289,7 @@ class TestGet(unittest.TestCase):
call_kwargs["headers"]["User-Agent"].startswith("posthog-python/")
)
@mock.patch("hanzoanalytics.request._session.get")
@mock.patch("hanzo_insights.request._session.get")
def test_get_passes_timeout(self, mock_get):
"""Test that timeout parameter is passed to the request."""
mock_response = requests.Response()
@@ -302,7 +302,7 @@ class TestGet(unittest.TestCase):
call_kwargs = mock_get.call_args[1]
self.assertEqual(call_kwargs["timeout"], 30)
@mock.patch("hanzoanalytics.request._session.get")
@mock.patch("hanzo_insights.request._session.get")
def test_get_constructs_full_url(self, mock_get):
"""Test that host and url are combined correctly."""
mock_response = requests.Response()
@@ -315,7 +315,7 @@ class TestGet(unittest.TestCase):
call_args = mock_get.call_args[0]
self.assertEqual(call_args[0], "https://example.com/api/flags")
@mock.patch("hanzoanalytics.request._session.get")
@mock.patch("hanzo_insights.request._session.get")
def test_get_removes_trailing_slash_from_host(self, mock_get):
"""Test that trailing slash is removed from host."""
mock_response = requests.Response()
@@ -332,20 +332,20 @@ class TestGet(unittest.TestCase):
@pytest.mark.parametrize(
"host, expected",
[
("https://t.hanzoanalytics.com", "https://t.hanzoanalytics.com"),
("https://t.hanzoanalytics.com/", "https://t.hanzoanalytics.com/"),
("t.hanzoanalytics.com", "t.hanzoanalytics.com"),
("t.hanzoanalytics.com/", "t.hanzoanalytics.com/"),
("https://us.hanzoanalytics.com.rg.proxy.com", "https://us.hanzoanalytics.com.rg.proxy.com"),
("app.hanzoanalytics.com", "app.hanzoanalytics.com"),
("eu.hanzoanalytics.com", "eu.hanzoanalytics.com"),
("https://app.hanzoanalytics.com", "https://us.i.hanzoanalytics.com"),
("https://eu.hanzoanalytics.com", "https://eu.i.hanzoanalytics.com"),
("https://us.hanzoanalytics.com", "https://us.i.hanzoanalytics.com"),
("https://app.hanzoanalytics.com/", "https://us.i.hanzoanalytics.com"),
("https://eu.hanzoanalytics.com/", "https://eu.i.hanzoanalytics.com"),
("https://us.hanzoanalytics.com/", "https://us.i.hanzoanalytics.com"),
(None, "https://us.i.hanzoanalytics.com"),
("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"),
],
)
def test_routing_to_custom_host(host, expected):
@@ -355,7 +355,7 @@ def test_routing_to_custom_host(host, expected):
def test_enable_keep_alive_sets_socket_options():
try:
enable_keep_alive()
from hanzoanalytics.request import _session
from hanzo_insights.request import _session
adapter = _session.get_adapter("https://example.com")
assert adapter.socket_options == KEEP_ALIVE_SOCKET_OPTIONS
@@ -367,7 +367,7 @@ def test_set_socket_options_clears_with_none():
try:
enable_keep_alive()
set_socket_options(None)
from hanzoanalytics.request import _session
from hanzo_insights.request import _session
adapter = _session.get_adapter("https://example.com")
assert adapter.socket_options is None
@@ -401,17 +401,17 @@ class TestFlagsSession(unittest.TestCase):
def test_retry_status_forcelist_excludes_rate_limits(self):
"""Verify 429 (rate limit) is NOT retried - need to wait, not hammer."""
from hanzoanalytics.request import RETRY_STATUS_FORCELIST
from hanzo_insights.request import RETRY_STATUS_FORCELIST
self.assertNotIn(429, RETRY_STATUS_FORCELIST)
def test_retry_status_forcelist_excludes_quota_errors(self):
"""Verify 402 (payment required/quota) is NOT retried - won't resolve."""
from hanzoanalytics.request import RETRY_STATUS_FORCELIST
from hanzo_insights.request import RETRY_STATUS_FORCELIST
self.assertNotIn(402, RETRY_STATUS_FORCELIST)
@mock.patch("hanzoanalytics.request._get_flags_session")
@mock.patch("hanzo_insights.request._get_flags_session")
def test_flags_uses_flags_session(self, mock_get_flags_session):
"""flags() uses the dedicated flags session, not the general session."""
mock_response = requests.Response()
@@ -428,13 +428,13 @@ 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.hanzoanalytics.com", distinct_id="user123")
result = flags("test-key", "https://test.hanzo_insights.com", distinct_id="user123")
self.assertEqual(result["featureFlags"]["test-flag"], True)
mock_get_flags_session.assert_called_once()
mock_session.post.assert_called_once()
@mock.patch("hanzoanalytics.request._get_flags_session")
@mock.patch("hanzo_insights.request._get_flags_session")
def test_flags_no_retry_on_quota_limit(self, mock_get_flags_session):
"""flags() raises QuotaLimitError without retrying (at application level)."""
mock_response = requests.Response()
@@ -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.hanzoanalytics.com", distinct_id="user123")
flags("test-key", "https://test.hanzo_insights.com", distinct_id="user123")
# QuotaLimitError is raised after response is received, not retried
self.assertEqual(mock_session.post.call_count, 1)
@@ -470,12 +470,12 @@ class TestFlagsSessionNetworkRetries(unittest.TestCase):
retries on network-level failures (DNS failures, connection refused,
connection reset, etc.) up to 2 times each.
"""
from hanzoanalytics.request import _build_flags_session
from hanzo_insights.request import _build_flags_session
session = _build_flags_session()
# Get the adapter for https://
adapter = session.get_adapter("https://test.hanzoanalytics.com")
adapter = session.get_adapter("https://test.hanzo_insights.com")
# Verify retry configuration
retry = adapter.max_retries
@@ -491,10 +491,10 @@ class TestFlagsSessionNetworkRetries(unittest.TestCase):
This tests the status_forcelist configuration which specifies
which HTTP status codes should trigger a retry.
"""
from hanzoanalytics.request import _build_flags_session, RETRY_STATUS_FORCELIST
from hanzo_insights.request import _build_flags_session, RETRY_STATUS_FORCELIST
session = _build_flags_session()
adapter = session.get_adapter("https://test.hanzoanalytics.com")
adapter = session.get_adapter("https://test.hanzo_insights.com")
retry = adapter.max_retries
# Verify the status codes that trigger retries
@@ -518,10 +518,10 @@ class TestFlagsSessionNetworkRetries(unittest.TestCase):
"""
Verify that retries use exponential backoff to avoid thundering herd.
"""
from hanzoanalytics.request import _build_flags_session
from hanzo_insights.request import _build_flags_session
session = _build_flags_session()
adapter = session.get_adapter("https://test.hanzoanalytics.com")
adapter = session.get_adapter("https://test.hanzo_insights.com")
retry = adapter.max_retries
self.assertEqual(
@@ -545,7 +545,7 @@ class TestFlagsSessionRetryIntegration(unittest.TestCase):
from http.server import HTTPServer, BaseHTTPRequestHandler
from socketserver import ThreadingMixIn
from urllib3.util.retry import Retry
from hanzoanalytics.request import HTTPAdapterWithSocketOptions, RETRY_STATUS_FORCELIST
from hanzo_insights.request import HTTPAdapterWithSocketOptions, RETRY_STATUS_FORCELIST
request_count = 0
@@ -631,7 +631,7 @@ class TestFlagsSessionRetryIntegration(unittest.TestCase):
import socket
import time
from urllib3.util.retry import Retry
from hanzoanalytics.request import HTTPAdapterWithSocketOptions, RETRY_STATUS_FORCELIST
from hanzo_insights.request import HTTPAdapterWithSocketOptions, RETRY_STATUS_FORCELIST
# Get an available port by binding then closing a socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@@ -2,7 +2,7 @@ import unittest
from parameterized import parameterized
from hanzoanalytics.types import (
from hanzo_insights.types import (
FeatureFlag,
FlagMetadata,
FlagReason,
@@ -14,7 +14,7 @@ from pydantic import BaseModel
from pydantic.v1 import BaseModel as BaseModelV1
from posthog import utils
from hanzoanalytics.types import FeatureFlagResult
from hanzo_insights.types import FeatureFlagResult
TEST_API_KEY = "kOOlRy2QlMY9jHZQv0bKz0FZyazBUoY8Arj0lFVNjs4"
FAKE_TEST_API_KEY = "random_key"
@@ -96,8 +96,8 @@ class TestUtils(unittest.TestCase):
@parameterized.expand(
[
("http://hanzoanalytics.io/", "http://hanzoanalytics.io"),
("http://hanzoanalytics.io", "http://hanzoanalytics.io"),
("http://hanzo_insights.io/", "http://hanzo_insights.io"),
("http://hanzo_insights.io", "http://hanzo_insights.io"),
("https://example.com/path/", "https://example.com/path"),
("https://example.com/path", "https://example.com/path"),
]
@@ -16,7 +16,7 @@ import distro # For Linux OS detection
import six
from dateutil.tz import tzlocal, tzutc
log = logging.getLogger("hanzoanalytics")
log = logging.getLogger("hanzo_insights")
def is_naive(dt):
-3
View File
@@ -1,3 +0,0 @@
from hanzoanalytics.ai.prompts import Prompts
__all__ = ["Prompts"]
@@ -51,7 +51,7 @@ async def test_async_exception_is_captured(asgi_app):
)
# Patch at the posthog module level where middleware imports from
with patch("hanzoanalytics.capture_exception", side_effect=mock_capture):
with patch("hanzo_insights.capture_exception", side_effect=mock_capture):
async with AsyncClient(
transport=ASGITransport(app=asgi_app), base_url="http://testserver"
) as ac:
@@ -93,7 +93,7 @@ async def test_sync_exception_is_captured(asgi_app):
)
# Patch at the posthog module level where middleware imports from
with patch("hanzoanalytics.capture_exception", side_effect=mock_capture):
with patch("hanzo_insights.capture_exception", side_effect=mock_capture):
async with AsyncClient(
transport=ASGITransport(app=asgi_app), base_url="http://testserver"
) as ac:
@@ -47,7 +47,7 @@ MIDDLEWARE = [
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"hanzoanalytics.integrations.django.PosthogContextMiddleware", # Test PostHog middleware
"hanzo_insights.integrations.django.PosthogContextMiddleware", # Test PostHog middleware
]
ROOT_URLCONF = "testdjango.urls"
@@ -125,5 +125,5 @@ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
# PostHog settings for testing
POSTHOG_API_KEY = "test-key"
POSTHOG_HOST = "https://app.hanzoanalytics.com"
POSTHOG_HOST = "https://app.hanzo_insights.com"
POSTHOG_MW_CAPTURE_EXCEPTIONS = True
+13 -13
View File
@@ -3,7 +3,7 @@ requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "hanzoanalytics"
name = "hanzo-insights"
version = "7.9.7"
description = "Integrate Hanzo Insights into any python application."
authors = [{ name = "Hanzo AI", email = "hey@hanzo.ai" }]
@@ -80,21 +80,21 @@ test = [
[tool.setuptools]
packages = [
"hanzoanalytics",
"hanzoanalytics.ai",
"hanzoanalytics.ai.langchain",
"hanzoanalytics.ai.openai",
"hanzoanalytics.ai.openai_agents",
"hanzoanalytics.ai.anthropic",
"hanzoanalytics.ai.gemini",
"hanzoanalytics.test",
"hanzoanalytics.test.ai",
"hanzoanalytics.test.ai.openai_agents",
"hanzoanalytics.integrations",
"hanzo_insights",
"hanzo_insights.ai",
"hanzo_insights.ai.langchain",
"hanzo_insights.ai.openai",
"hanzo_insights.ai.openai_agents",
"hanzo_insights.ai.anthropic",
"hanzo_insights.ai.gemini",
"hanzo_insights.test",
"hanzo_insights.test.ai",
"hanzo_insights.test.ai.openai_agents",
"hanzo_insights.integrations",
]
[tool.pytest.ini_options]
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "function"
testpaths = ["hanzoanalytics/test"]
testpaths = ["hanzo_insights/test"]
norecursedirs = ["integration_tests"]
+6 -6
View File
@@ -14,8 +14,8 @@ from typing import Any, Dict, List, Optional
from flask import Flask, jsonify, request
from posthog import Client
from hanzoanalytics.request import batch_post as original_batch_post
from hanzoanalytics.version import VERSION
from hanzo_insights.request import batch_post as original_batch_post
from hanzo_insights.version import VERSION
# Configure logging
logging.basicConfig(
@@ -178,14 +178,14 @@ def patched_batch_post(
# Monkey-patch the batch_post function
import hanzoanalytics.request # noqa: E402
import hanzo_insights.request # noqa: E402
hanzoanalytics.request.batch_post = patched_batch_post
hanzo_insights.request.batch_post = patched_batch_post
# Also patch in consumer module
import hanzoanalytics.consumer # noqa: E402
import hanzo_insights.consumer # noqa: E402
hanzoanalytics.consumer.batch_post = patched_batch_post
hanzo_insights.consumer.batch_post = patched_batch_post
@app.route("/health", methods=["GET"])
+3 -3
View File
@@ -7,18 +7,18 @@ except ImportError:
from distutils.core import setup
# Don't import module here, since deps may not be installed
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "hanzoanalytics"))
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "hanzo_insights"))
from version import VERSION # noqa: E402
long_description = """
Hanzo Insights is developer-friendly, self-hosted product analytics.
hanzoanalytics is the python package.
hanzo_insights is the python package.
This package requires Python 3.10 or higher.
"""
setup(
name="hanzoanalytics",
name="hanzo_insights",
version=VERSION,
url="https://github.com/hanzoai/insights",
author="Hanzo AI",
+6 -6
View File
@@ -25,14 +25,14 @@ with open("pyproject.toml", "rb") as f:
config["project"]["name"] = "posthoganalytics"
config["project"]["readme"] = "README_ANALYTICS.md"
# Rename packages from hanzoanalytics.* to posthoganalytics.*
# Rename packages from hanzo_insights.* to posthoganalytics.*
if "packages" in config["tool"]["setuptools"]:
new_packages = []
for package in config["tool"]["setuptools"]["packages"]:
if package == "hanzoanalytics":
if package == "hanzo_insights":
new_packages.append("posthoganalytics")
elif package.startswith("hanzoanalytics."):
new_packages.append(package.replace("hanzoanalytics.", "posthoganalytics.", 1))
elif package.startswith("hanzo_insights."):
new_packages.append(package.replace("hanzo_insights.", "posthoganalytics.", 1))
else:
new_packages.append(package)
config["tool"]["setuptools"]["packages"] = new_packages
@@ -56,9 +56,9 @@ setup(
# Basic fields for backward compatibility
url="https://github.com/posthog/posthog-python",
author="Posthog",
author_email="hey@hanzoanalytics.com",
author_email="hey@hanzo_insights.com",
maintainer="PostHog",
maintainer_email="hey@hanzoanalytics.com",
maintainer_email="hey@hanzo_insights.com",
license="MIT License",
description="Integrate PostHog into any python application.",
long_description=long_description,