Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-posthog for openSUSE:Factory 
checked in at 2026-03-04 21:05:32
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-posthog (Old)
 and      /work/SRC/openSUSE:Factory/.python-posthog.new.561 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-posthog"

Wed Mar  4 21:05:32 2026 rev:5 rq:1336189 version:7.9.6

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-posthog/python-posthog.changes    
2026-02-20 17:52:34.544413931 +0100
+++ /work/SRC/openSUSE:Factory/.python-posthog.new.561/python-posthog.changes   
2026-03-04 21:05:35.093343830 +0100
@@ -1,0 +2,12 @@
+Tue Mar  3 19:08:13 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 7.9.6:
+  * refactor: add PROPERTY_OPERATORS constant for match_property
+  * feat: add semver targeting support to local flag evaluation
+  * fix: use $ip not $ip_addess
+  * feat(llma): add $ai_tokens_source property to detect token
+    value overrides
+  * fix: revert manual release and add sampo changeset for
+    ai_tokens_source
+
+-------------------------------------------------------------------

Old:
----
  posthog-7.9.3.tar.gz

New:
----
  posthog-7.9.6.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-posthog.spec ++++++
--- /var/tmp/diff_new_pack.5xi1KU/_old  2026-03-04 21:05:37.245432750 +0100
+++ /var/tmp/diff_new_pack.5xi1KU/_new  2026-03-04 21:05:37.261433411 +0100
@@ -18,7 +18,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-posthog
-Version:        7.9.3
+Version:        7.9.6
 Release:        0
 Summary:        PostHog is developer-friendly, self-hosted product analytics
 License:        MIT

++++++ posthog-7.9.3.tar.gz -> posthog-7.9.6.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/posthog-7.9.3/PKG-INFO new/posthog-7.9.6/PKG-INFO
--- old/posthog-7.9.3/PKG-INFO  2026-02-18 23:20:11.462619500 +0100
+++ new/posthog-7.9.6/PKG-INFO  2026-03-02 22:28:48.419109000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: posthog
-Version: 7.9.3
+Version: 7.9.6
 Summary: Integrate PostHog into any python application.
 Home-page: https://github.com/posthog/posthog-python
 Author: Posthog
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/posthog-7.9.3/posthog/ai/utils.py 
new/posthog-7.9.6/posthog/ai/utils.py
--- old/posthog-7.9.3/posthog/ai/utils.py       2026-02-18 23:18:08.000000000 
+0100
+++ new/posthog-7.9.6/posthog/ai/utils.py       2026-03-02 22:28:23.000000000 
+0100
@@ -13,6 +13,28 @@
 from posthog.client import Client as PostHogClient
 
 
+_TOKEN_PROPERTY_KEYS = frozenset(
+    {
+        "$ai_input_tokens",
+        "$ai_output_tokens",
+        "$ai_cache_read_input_tokens",
+        "$ai_cache_creation_input_tokens",
+        "$ai_total_tokens",
+        "$ai_reasoning_tokens",
+    }
+)
+
+
+def _get_tokens_source(
+    sdk_tags: Dict[str, Any], posthog_properties: Optional[Dict[str, Any]]
+) -> str:
+    if posthog_properties and any(
+        key in posthog_properties for key in _TOKEN_PROPERTY_KEYS
+    ):
+        return "passthrough"
+    return "sdk"
+
+
 def serialize_raw_usage(raw_usage: Any) -> Optional[Dict[str, Any]]:
     """
     Convert raw provider usage objects to JSON-serializable dicts.
@@ -413,14 +435,19 @@
 
             # send the event to posthog
             if hasattr(ph_client, "capture") and callable(ph_client.capture):
+                sdk_tags = get_tags()
+                merged_properties = {
+                    **sdk_tags,
+                    **(posthog_properties or {}),
+                    **(error_params or {}),
+                }
+                merged_properties["$ai_tokens_source"] = _get_tokens_source(
+                    sdk_tags, posthog_properties
+                )
                 ph_client.capture(
                     distinct_id=posthog_distinct_id or posthog_trace_id,
                     event="$ai_generation",
-                    properties={
-                        **get_tags(),
-                        **(posthog_properties or {}),
-                        **(error_params or {}),
-                    },
+                    properties=merged_properties,
                     groups=posthog_groups,
                 )
 
@@ -543,14 +570,19 @@
 
             # send the event to posthog
             if hasattr(ph_client, "capture") and callable(ph_client.capture):
+                sdk_tags = get_tags()
+                merged_properties = {
+                    **sdk_tags,
+                    **(posthog_properties or {}),
+                    **(error_params or {}),
+                }
+                merged_properties["$ai_tokens_source"] = _get_tokens_source(
+                    sdk_tags, posthog_properties
+                )
                 ph_client.capture(
                     distinct_id=posthog_distinct_id or posthog_trace_id,
                     event="$ai_generation",
-                    properties={
-                        **get_tags(),
-                        **(posthog_properties or {}),
-                        **(error_params or {}),
-                    },
+                    properties=merged_properties,
                     groups=posthog_groups,
                 )
 
@@ -627,6 +659,15 @@
         **(event_data.get("properties") or {}),
     }
 
+    # Determine token source: SDK-computed vs externally overridden
+    sdk_token_tags = {
+        "$ai_input_tokens": event_data["usage_stats"].get("input_tokens", 0),
+        "$ai_output_tokens": event_data["usage_stats"].get("output_tokens", 0),
+    }
+    event_properties["$ai_tokens_source"] = _get_tokens_source(
+        sdk_token_tags, event_data.get("properties")
+    )
+
     # Extract and add tools based on provider
     available_tools = extract_available_tool_calls(
         event_data["provider"],
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/posthog-7.9.3/posthog/feature_flags.py 
new/posthog-7.9.6/posthog/feature_flags.py
--- old/posthog-7.9.3/posthog/feature_flags.py  2026-02-18 23:18:08.000000000 
+0100
+++ new/posthog-7.9.6/posthog/feature_flags.py  2026-03-02 22:28:23.000000000 
+0100
@@ -18,6 +18,30 @@
 
 NONE_VALUES_ALLOWED_OPERATORS = ["is_not"]
 
+# All operators supported by match_property, grouped by category.
+EQUALITY_OPERATORS = ("exact", "is_not", "is_set", "is_not_set")
+STRING_OPERATORS = ("icontains", "not_icontains", "regex", "not_regex")
+NUMERIC_OPERATORS = ("gt", "gte", "lt", "lte")
+DATE_OPERATORS = ("is_date_before", "is_date_after")
+SEMVER_COMPARISON_OPERATORS = (
+    "semver_eq",
+    "semver_neq",
+    "semver_gt",
+    "semver_gte",
+    "semver_lt",
+    "semver_lte",
+)
+SEMVER_RANGE_OPERATORS = ("semver_tilde", "semver_caret", "semver_wildcard")
+SEMVER_OPERATORS = SEMVER_COMPARISON_OPERATORS + SEMVER_RANGE_OPERATORS
+
+PROPERTY_OPERATORS = (
+    EQUALITY_OPERATORS
+    + STRING_OPERATORS
+    + NUMERIC_OPERATORS
+    + DATE_OPERATORS
+    + SEMVER_OPERATORS
+)
+
 
 class InconclusiveMatchError(Exception):
     pass
@@ -385,6 +409,9 @@
     operator = property.get("operator") or "exact"
     value = property.get("value")
 
+    if operator not in PROPERTY_OPERATORS:
+        raise InconclusiveMatchError(f"Unknown operator {operator}")
+
     if key not in property_values:
         raise InconclusiveMatchError(
             "can't match properties without a given property value"
@@ -505,7 +532,64 @@
                 "The date provided must be a string or date object"
             )
 
-    # if we get here, we don't know how to handle the operator
+    if operator in SEMVER_OPERATORS:
+        try:
+            override_parsed = parse_semver(override_value)
+        except (ValueError, TypeError):
+            raise InconclusiveMatchError(
+                f"Person property value '{override_value}' is not a valid 
semver"
+            )
+
+        if operator in SEMVER_COMPARISON_OPERATORS:
+            try:
+                flag_parsed = parse_semver(value)
+            except (ValueError, TypeError):
+                raise InconclusiveMatchError(
+                    f"Flag semver value '{value}' is not a valid semver"
+                )
+
+            if operator == "semver_eq":
+                return override_parsed == flag_parsed
+            elif operator == "semver_neq":
+                return override_parsed != flag_parsed
+            elif operator == "semver_gt":
+                return override_parsed > flag_parsed
+            elif operator == "semver_gte":
+                return override_parsed >= flag_parsed
+            elif operator == "semver_lt":
+                return override_parsed < flag_parsed
+            elif operator == "semver_lte":
+                return override_parsed <= flag_parsed
+
+        elif operator == "semver_tilde":
+            try:
+                lower, upper = _tilde_bounds(str(value))
+            except (ValueError, TypeError):
+                raise InconclusiveMatchError(
+                    f"Flag semver value '{value}' is not valid for tilde 
operator"
+                )
+            return lower <= override_parsed < upper
+
+        elif operator == "semver_caret":
+            try:
+                lower, upper = _caret_bounds(str(value))
+            except (ValueError, TypeError):
+                raise InconclusiveMatchError(
+                    f"Flag semver value '{value}' is not valid for caret 
operator"
+                )
+            return lower <= override_parsed < upper
+
+        elif operator == "semver_wildcard":
+            try:
+                lower, upper = _wildcard_bounds(str(value))
+            except (ValueError, TypeError):
+                raise InconclusiveMatchError(
+                    f"Flag semver value '{value}' is not valid for wildcard 
operator"
+                )
+            return lower <= override_parsed < upper
+
+    # Unreachable: all operators in PROPERTY_OPERATORS are handled above,
+    # and unknown operators are rejected at the top of this function.
     raise InconclusiveMatchError(f"Unknown operator {operator}")
 
 
@@ -686,3 +770,75 @@
         return parsed_dt
     else:
         return None
+
+
+def parse_semver(value: str) -> tuple:
+    """Parse a semver string into a comparable (major, minor, patch) integer 
tuple.
+
+    Matches the behavior of the sortableSemver HogQL function:
+    - Handles v-prefix, whitespace, pre-release suffixes
+    - Defaults missing components to 0 (e.g., 1.2 -> 1.2.0)
+    Raises ValueError if parsing fails.
+    """
+    text = str(value).strip().lstrip("vV")
+    # Strip pre-release/build metadata suffix
+    text = text.split("-")[0].split("+")[0]
+    parts = text.split(".")
+
+    if not parts or not parts[0]:
+        raise ValueError("Invalid semver format")
+
+    major = int(parts[0])
+    minor = int(parts[1]) if len(parts) > 1 and parts[1] else 0
+    patch = int(parts[2]) if len(parts) > 2 and parts[2] else 0
+
+    return (major, minor, patch)
+
+
+def _tilde_bounds(value: str) -> tuple:
+    """~1.2.3 means >=1.2.3 <1.3.0 (allows patch-level changes)."""
+    major, minor, patch = parse_semver(value)
+    return (major, minor, patch), (major, minor + 1, 0)
+
+
+def _caret_bounds(value: str) -> tuple:
+    """Caret follows semver spec:
+    ^1.2.3 means >=1.2.3 <2.0.0
+    ^0.2.3 means >=0.2.3 <0.3.0
+    ^0.0.3 means >=0.0.3 <0.0.4
+    """
+    major, minor, patch = parse_semver(value)
+    lower = (major, minor, patch)
+
+    if major > 0:
+        upper = (major + 1, 0, 0)
+    elif minor > 0:
+        upper = (0, minor + 1, 0)
+    else:
+        upper = (0, 0, patch + 1)
+
+    return lower, upper
+
+
+def _wildcard_bounds(value: str) -> tuple:
+    """Wildcard matching:
+    1.* means >=1.0.0 <2.0.0
+    1.2.* means >=1.2.0 <1.3.0
+    """
+    cleaned = str(value).strip().lstrip("vV").replace("*", "").rstrip(".")
+    if not cleaned:
+        raise ValueError("Invalid wildcard pattern")
+
+    parts = [p for p in cleaned.split(".") if p]
+    if not parts:
+        raise ValueError("Invalid wildcard pattern")
+
+    if len(parts) == 1:
+        major = int(parts[0])
+        return (major, 0, 0), (major + 1, 0, 0)
+    elif len(parts) == 2:
+        major, minor = int(parts[0]), int(parts[1])
+        return (major, minor, 0), (major, minor + 1, 0)
+    else:
+        major, minor, patch = int(parts[0]), int(parts[1]), int(parts[2])
+        return (major, minor, patch), (major, minor, patch + 1)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/posthog-7.9.3/posthog/integrations/django.py 
new/posthog-7.9.6/posthog/integrations/django.py
--- old/posthog-7.9.3/posthog/integrations/django.py    2026-02-18 
23:18:08.000000000 +0100
+++ new/posthog-7.9.6/posthog/integrations/django.py    2026-03-02 
22:28:23.000000000 +0100
@@ -155,7 +155,7 @@
         # Extract IP address
         ip_address = request.headers.get("X-Forwarded-For")
         if ip_address:
-            tags["$ip_address"] = ip_address
+            tags["$ip"] = ip_address
 
         # Extract user agent
         user_agent = request.headers.get("User-Agent")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/posthog-7.9.3/posthog/test/ai/test_tokens_source.py 
new/posthog-7.9.6/posthog/test/ai/test_tokens_source.py
--- old/posthog-7.9.3/posthog/test/ai/test_tokens_source.py     1970-01-01 
01:00:00.000000000 +0100
+++ new/posthog-7.9.6/posthog/test/ai/test_tokens_source.py     2026-03-02 
22:28:23.000000000 +0100
@@ -0,0 +1,62 @@
+from parameterized import parameterized
+
+from posthog.ai.utils import _get_tokens_source
+
+
[email protected](
+    [
+        ("no_posthog_properties", {"$ai_input_tokens": 100}, None, "sdk"),
+        ("empty_posthog_properties", {"$ai_input_tokens": 100}, {}, "sdk"),
+        (
+            "unrelated_posthog_properties",
+            {"$ai_input_tokens": 100},
+            {"foo": "bar"},
+            "sdk",
+        ),
+        (
+            "override_input_tokens",
+            {"$ai_input_tokens": 100},
+            {"$ai_input_tokens": 999},
+            "passthrough",
+        ),
+        (
+            "override_output_tokens",
+            {"$ai_output_tokens": 50},
+            {"$ai_output_tokens": 999},
+            "passthrough",
+        ),
+        (
+            "override_total_tokens",
+            {"$ai_input_tokens": 100},
+            {"$ai_total_tokens": 999},
+            "passthrough",
+        ),
+        (
+            "override_cache_read",
+            {"$ai_input_tokens": 100},
+            {"$ai_cache_read_input_tokens": 500},
+            "passthrough",
+        ),
+        (
+            "override_cache_creation",
+            {"$ai_input_tokens": 100},
+            {"$ai_cache_creation_input_tokens": 200},
+            "passthrough",
+        ),
+        (
+            "override_reasoning_tokens",
+            {"$ai_input_tokens": 100},
+            {"$ai_reasoning_tokens": 300},
+            "passthrough",
+        ),
+        (
+            "mixed_override_and_custom",
+            {"$ai_input_tokens": 100},
+            {"$ai_input_tokens": 999, "custom_key": "value"},
+            "passthrough",
+        ),
+    ]
+)
+def test_get_tokens_source(name, sdk_tags, posthog_properties, expected):
+    result = _get_tokens_source(sdk_tags, posthog_properties)
+    assert result == expected
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/posthog-7.9.3/posthog/test/test_feature_flags.py 
new/posthog-7.9.6/posthog/test/test_feature_flags.py
--- old/posthog-7.9.3/posthog/test/test_feature_flags.py        2026-02-18 
23:18:08.000000000 +0100
+++ new/posthog-7.9.6/posthog/test/test_feature_flags.py        2026-03-02 
22:28:23.000000000 +0100
@@ -4198,6 +4198,207 @@
         with self.assertRaises(InconclusiveMatchError):
             self.assertFalse(match_property(property_k, {"key": "random"}))
 
+    def test_match_properties_semver_eq(self):
+        prop = self.property(key="version", value="1.2.3", 
operator="semver_eq")
+        self.assertTrue(match_property(prop, {"version": "1.2.3"}))
+        self.assertFalse(match_property(prop, {"version": "1.2.4"}))
+        self.assertFalse(match_property(prop, {"version": "1.2.2"}))
+        self.assertFalse(match_property(prop, {"version": "2.0.0"}))
+
+        # Pre-release suffix is stripped for comparison
+        self.assertTrue(match_property(prop, {"version": "1.2.3-alpha.1"}))
+
+        # Partial versions default missing parts to 0
+        prop_partial = self.property(key="version", value="1.2", 
operator="semver_eq")
+        self.assertTrue(match_property(prop_partial, {"version": "1.2.0"}))
+        self.assertFalse(match_property(prop_partial, {"version": "1.2.1"}))
+
+    def test_match_properties_semver_neq(self):
+        prop = self.property(key="version", value="1.2.3", 
operator="semver_neq")
+        self.assertFalse(match_property(prop, {"version": "1.2.3"}))
+        self.assertTrue(match_property(prop, {"version": "1.2.4"}))
+        self.assertTrue(match_property(prop, {"version": "2.0.0"}))
+
+    def test_match_properties_semver_gt(self):
+        prop = self.property(key="version", value="1.2.3", 
operator="semver_gt")
+        self.assertTrue(match_property(prop, {"version": "1.2.4"}))
+        self.assertTrue(match_property(prop, {"version": "1.3.0"}))
+        self.assertTrue(match_property(prop, {"version": "2.0.0"}))
+        self.assertFalse(match_property(prop, {"version": "1.2.3"}))
+        self.assertFalse(match_property(prop, {"version": "1.2.2"}))
+        self.assertFalse(match_property(prop, {"version": "0.9.0"}))
+
+    def test_match_properties_semver_gte(self):
+        prop = self.property(key="version", value="1.2.3", 
operator="semver_gte")
+        self.assertTrue(match_property(prop, {"version": "1.2.3"}))
+        self.assertTrue(match_property(prop, {"version": "1.2.4"}))
+        self.assertTrue(match_property(prop, {"version": "2.0.0"}))
+        self.assertFalse(match_property(prop, {"version": "1.2.2"}))
+        self.assertFalse(match_property(prop, {"version": "0.9.0"}))
+
+    def test_match_properties_semver_lt(self):
+        prop = self.property(key="version", value="1.2.3", 
operator="semver_lt")
+        self.assertTrue(match_property(prop, {"version": "1.2.2"}))
+        self.assertTrue(match_property(prop, {"version": "1.1.0"}))
+        self.assertTrue(match_property(prop, {"version": "0.9.0"}))
+        self.assertFalse(match_property(prop, {"version": "1.2.3"}))
+        self.assertFalse(match_property(prop, {"version": "1.2.4"}))
+        self.assertFalse(match_property(prop, {"version": "2.0.0"}))
+
+    def test_match_properties_semver_lte(self):
+        prop = self.property(key="version", value="1.2.3", 
operator="semver_lte")
+        self.assertTrue(match_property(prop, {"version": "1.2.3"}))
+        self.assertTrue(match_property(prop, {"version": "1.2.2"}))
+        self.assertTrue(match_property(prop, {"version": "0.9.0"}))
+        self.assertFalse(match_property(prop, {"version": "1.2.4"}))
+        self.assertFalse(match_property(prop, {"version": "2.0.0"}))
+
+    def test_match_properties_semver_tilde(self):
+        # ~1.2.3 means >=1.2.3 <1.3.0
+        prop = self.property(key="version", value="1.2.3", 
operator="semver_tilde")
+        self.assertTrue(match_property(prop, {"version": "1.2.3"}))
+        self.assertTrue(match_property(prop, {"version": "1.2.5"}))
+        self.assertTrue(match_property(prop, {"version": "1.2.99"}))
+        self.assertFalse(match_property(prop, {"version": "1.3.0"}))
+        self.assertFalse(match_property(prop, {"version": "1.2.2"}))
+        self.assertFalse(match_property(prop, {"version": "2.0.0"}))
+
+    def test_match_properties_semver_caret(self):
+        # ^1.2.3 means >=1.2.3 <2.0.0
+        prop = self.property(key="version", value="1.2.3", 
operator="semver_caret")
+        self.assertTrue(match_property(prop, {"version": "1.2.3"}))
+        self.assertTrue(match_property(prop, {"version": "1.9.0"}))
+        self.assertTrue(match_property(prop, {"version": "1.99.99"}))
+        self.assertFalse(match_property(prop, {"version": "2.0.0"}))
+        self.assertFalse(match_property(prop, {"version": "1.2.2"}))
+        self.assertFalse(match_property(prop, {"version": "0.9.0"}))
+
+        # ^0.2.3 means >=0.2.3 <0.3.0 (leftmost non-zero is minor)
+        prop_zero_major = self.property(
+            key="version", value="0.2.3", operator="semver_caret"
+        )
+        self.assertTrue(match_property(prop_zero_major, {"version": "0.2.3"}))
+        self.assertTrue(match_property(prop_zero_major, {"version": "0.2.9"}))
+        self.assertFalse(match_property(prop_zero_major, {"version": "0.3.0"}))
+        self.assertFalse(match_property(prop_zero_major, {"version": "1.0.0"}))
+
+        # ^0.0.3 means >=0.0.3 <0.0.4 (leftmost non-zero is patch)
+        prop_zero_minor = self.property(
+            key="version", value="0.0.3", operator="semver_caret"
+        )
+        self.assertTrue(match_property(prop_zero_minor, {"version": "0.0.3"}))
+        self.assertFalse(match_property(prop_zero_minor, {"version": "0.0.4"}))
+        self.assertFalse(match_property(prop_zero_minor, {"version": "0.1.0"}))
+
+    def test_match_properties_semver_wildcard(self):
+        # 1.2.* means >=1.2.0 <1.3.0
+        prop = self.property(key="version", value="1.2.*", 
operator="semver_wildcard")
+        self.assertTrue(match_property(prop, {"version": "1.2.0"}))
+        self.assertTrue(match_property(prop, {"version": "1.2.5"}))
+        self.assertTrue(match_property(prop, {"version": "1.2.99"}))
+        self.assertFalse(match_property(prop, {"version": "1.3.0"}))
+        self.assertFalse(match_property(prop, {"version": "1.1.9"}))
+        self.assertFalse(match_property(prop, {"version": "2.0.0"}))
+
+        # 1.* means >=1.0.0 <2.0.0
+        prop_major = self.property(
+            key="version", value="1.*", operator="semver_wildcard"
+        )
+        self.assertTrue(match_property(prop_major, {"version": "1.0.0"}))
+        self.assertTrue(match_property(prop_major, {"version": "1.99.99"}))
+        self.assertFalse(match_property(prop_major, {"version": "2.0.0"}))
+        self.assertFalse(match_property(prop_major, {"version": "0.9.0"}))
+
+    def test_match_properties_semver_with_prerelease(self):
+        # Pre-release suffixes are stripped before comparison
+        prop = self.property(key="version", value="1.2.3", 
operator="semver_gt")
+        self.assertTrue(match_property(prop, {"version": "1.3.0-beta.1"}))
+        self.assertFalse(match_property(prop, {"version": "1.2.2-rc.1"}))
+
+        # Flag value can also have pre-release suffix
+        prop_pre = self.property(
+            key="version", value="1.2.3-alpha", operator="semver_gte"
+        )
+        self.assertTrue(match_property(prop_pre, {"version": "1.2.3"}))
+        self.assertTrue(match_property(prop_pre, {"version": "2.0.0"}))
+        self.assertFalse(match_property(prop_pre, {"version": "1.2.2"}))
+
+    def test_match_properties_semver_edge_cases(self):
+        """Test semver parsing handles v-prefix, whitespace, leading zeros, 
and other common formats."""
+        prop = self.property(key="version", value="1.2.3", 
operator="semver_eq")
+
+        # v-prefix: "v1.2.3" -> extracts "1.2.3"
+        self.assertTrue(match_property(prop, {"version": "v1.2.3"}))
+
+        # Leading space: " 1.2.3" -> extracts "1.2.3"
+        self.assertTrue(match_property(prop, {"version": " 1.2.3"}))
+
+        # Trailing space: "1.2.3 " -> extracts "1.2.3"
+        self.assertTrue(match_property(prop, {"version": "1.2.3 "}))
+
+        # Leading zeros: "01.02.03" -> int("01")=1, int("02")=2, int("03")=3
+        self.assertTrue(match_property(prop, {"version": "01.02.03"}))
+
+        # Flag value with v-prefix
+        prop_v = self.property(key="version", value="v1.2.3", 
operator="semver_eq")
+        self.assertTrue(match_property(prop_v, {"version": "1.2.3"}))
+
+        # 0.0.0 minimal version
+        prop_min = self.property(key="version", value="0.0.0", 
operator="semver_eq")
+        self.assertTrue(match_property(prop_min, {"version": "0.0.0"}))
+
+        prop_gt_min = self.property(key="version", value="0.0.0", 
operator="semver_gt")
+        self.assertTrue(match_property(prop_gt_min, {"version": "0.0.1"}))
+        self.assertFalse(match_property(prop_gt_min, {"version": "0.0.0"}))
+
+        # 4-part version: regex extracts "1.2.3.4" -> takes first 3 parts
+        prop_four = self.property(key="version", value="1.2.3", 
operator="semver_eq")
+        self.assertTrue(match_property(prop_four, {"version": "1.2.3.4"}))
+
+        # Truly invalid values raise InconclusiveMatchError
+        with self.assertRaises(InconclusiveMatchError):
+            match_property(prop, {"version": "abc"})
+
+        with self.assertRaises(InconclusiveMatchError):
+            match_property(prop, {"version": ""})
+
+        # Leading dot: ".1.2.3" -> invalid, empty first component
+        with self.assertRaises(InconclusiveMatchError):
+            match_property(prop, {"version": ".1.2.3"})
+
+        # Caret with v-prefix in flag value
+        prop_caret_v = self.property(
+            key="version", value="v1.2.3", operator="semver_caret"
+        )
+        self.assertTrue(match_property(prop_caret_v, {"version": "1.5.0"}))
+        self.assertFalse(match_property(prop_caret_v, {"version": "2.0.0"}))
+
+        # Wildcard with v-prefix in property value
+        prop_wild = self.property(
+            key="version", value="1.2.*", operator="semver_wildcard"
+        )
+        self.assertTrue(match_property(prop_wild, {"version": "v1.2.5"}))
+        self.assertFalse(match_property(prop_wild, {"version": "v1.3.0"}))
+
+    def test_match_properties_semver_invalid_values(self):
+        prop = self.property(key="version", value="1.2.3", 
operator="semver_eq")
+
+        # Invalid person property value
+        with self.assertRaises(InconclusiveMatchError):
+            match_property(prop, {"version": "not-a-version"})
+
+        # Missing key
+        with self.assertRaises(InconclusiveMatchError):
+            match_property(prop, {"other_key": "1.2.3"})
+
+        # None override value returns False (handled before semver logic)
+        self.assertFalse(match_property(prop, {"version": None}))
+
+        # Invalid flag value
+        prop_bad = self.property(key="version", value="not-valid", 
operator="semver_gt")
+        with self.assertRaises(InconclusiveMatchError):
+            match_property(prop_bad, {"version": "1.2.3"})
+
     def test_unknown_operator(self):
         property_a = self.property(key="key", value="2022-05-01", 
operator="is_unknown")
         with self.assertRaises(InconclusiveMatchError) as exception_context:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/posthog-7.9.3/posthog/version.py 
new/posthog-7.9.6/posthog/version.py
--- old/posthog-7.9.3/posthog/version.py        2026-02-18 23:20:09.000000000 
+0100
+++ new/posthog-7.9.6/posthog/version.py        2026-03-02 22:28:45.000000000 
+0100
@@ -1 +1 @@
-VERSION = "7.9.3"
+VERSION = "7.9.6"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/posthog-7.9.3/posthog.egg-info/PKG-INFO 
new/posthog-7.9.6/posthog.egg-info/PKG-INFO
--- old/posthog-7.9.3/posthog.egg-info/PKG-INFO 2026-02-18 23:20:11.000000000 
+0100
+++ new/posthog-7.9.6/posthog.egg-info/PKG-INFO 2026-03-02 22:28:48.000000000 
+0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: posthog
-Version: 7.9.3
+Version: 7.9.6
 Summary: Integrate PostHog into any python application.
 Home-page: https://github.com/posthog/posthog-python
 Author: Posthog
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/posthog-7.9.3/posthog.egg-info/SOURCES.txt 
new/posthog-7.9.6/posthog.egg-info/SOURCES.txt
--- old/posthog-7.9.3/posthog.egg-info/SOURCES.txt      2026-02-18 
23:20:11.000000000 +0100
+++ new/posthog-7.9.6/posthog.egg-info/SOURCES.txt      2026-03-02 
22:28:48.000000000 +0100
@@ -67,5 +67,6 @@
 posthog/test/ai/test_prompts.py
 posthog/test/ai/test_sanitization.py
 posthog/test/ai/test_system_prompts.py
+posthog/test/ai/test_tokens_source.py
 posthog/test/ai/openai_agents/__init__.py
 posthog/test/ai/openai_agents/test_processor.py
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/posthog-7.9.3/pyproject.toml 
new/posthog-7.9.6/pyproject.toml
--- old/posthog-7.9.3/pyproject.toml    2026-02-18 23:20:08.000000000 +0100
+++ new/posthog-7.9.6/pyproject.toml    2026-03-02 22:28:45.000000000 +0100
@@ -4,7 +4,7 @@
 
 [project]
 name = "posthog"
-version = "7.9.3"
+version = "7.9.6"
 description = "Integrate PostHog into any python application."
 authors = [{ name = "PostHog", email = "[email protected]" }]
 maintainers = [{ name = "PostHog", email = "[email protected]" }]

Reply via email to