Skip to content

Commit 8d83315

Browse files
dmarticusclaude
andauthored
refactor: add PROPERTY_OPERATORS constant for match_property (#448)
* feat: add semver targeting support to local flag evaluation Implement 9 semver comparison operators (semver_eq, semver_neq, semver_gt, semver_gte, semver_lt, semver_lte, semver_tilde, semver_caret, semver_wildcard) for feature flag local evaluation. Uses regex-based parsing that matches the server-side sortableSemver behavior to handle v-prefix, whitespace, pre-release suffixes, and non-standard version formats. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> * fix: guard against ReDoS in semver regex parsing Add input length limit before regex search to prevent polynomial backtracking on adversarial input (CodeQL py/polynomial-redos). * fix: replace regex with string parsing to resolve ReDoS warning Replace SEMVER_EXTRACT_RE regex with simple string splitting to eliminate nested quantifiers that CodeQL flagged as polynomial-redos. * refactor: inline semver operator tuple to match existing patterns * refactor: add PROPERTY_OPERATORS constant for match_property Extract all operator strings into a single source-of-truth tuple and validate against it early in match_property, replacing the fallthrough at the end of the function. * refactor: split PROPERTY_OPERATORS into composable sub-groups Break the flat tuple into category-specific tuples (EQUALITY_OPERATORS, STRING_OPERATORS, etc.) that compose into PROPERTY_OPERATORS via concatenation. The semver dispatch code now references SEMVER_OPERATORS and SEMVER_COMPARISON_OPERATORS instead of repeating the full operator lists inline. * fix: add unreachable fallthrough to satisfy mypy return check * add release --------- Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
1 parent 1e1e566 commit 8d83315

2 files changed

Lines changed: 36 additions & 20 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
pypi/posthog: patch
3+
---
4+
5+
add PROPERTY_OPERATORS constant for match_property

posthog/feature_flags.py

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,30 @@
1818

1919
NONE_VALUES_ALLOWED_OPERATORS = ["is_not"]
2020

21+
# All operators supported by match_property, grouped by category.
22+
EQUALITY_OPERATORS = ("exact", "is_not", "is_set", "is_not_set")
23+
STRING_OPERATORS = ("icontains", "not_icontains", "regex", "not_regex")
24+
NUMERIC_OPERATORS = ("gt", "gte", "lt", "lte")
25+
DATE_OPERATORS = ("is_date_before", "is_date_after")
26+
SEMVER_COMPARISON_OPERATORS = (
27+
"semver_eq",
28+
"semver_neq",
29+
"semver_gt",
30+
"semver_gte",
31+
"semver_lt",
32+
"semver_lte",
33+
)
34+
SEMVER_RANGE_OPERATORS = ("semver_tilde", "semver_caret", "semver_wildcard")
35+
SEMVER_OPERATORS = SEMVER_COMPARISON_OPERATORS + SEMVER_RANGE_OPERATORS
36+
37+
PROPERTY_OPERATORS = (
38+
EQUALITY_OPERATORS
39+
+ STRING_OPERATORS
40+
+ NUMERIC_OPERATORS
41+
+ DATE_OPERATORS
42+
+ SEMVER_OPERATORS
43+
)
44+
2145

2246
class InconclusiveMatchError(Exception):
2347
pass
@@ -385,6 +409,9 @@ def match_property(property, property_values) -> bool:
385409
operator = property.get("operator") or "exact"
386410
value = property.get("value")
387411

412+
if operator not in PROPERTY_OPERATORS:
413+
raise InconclusiveMatchError(f"Unknown operator {operator}")
414+
388415
if key not in property_values:
389416
raise InconclusiveMatchError(
390417
"can't match properties without a given property value"
@@ -505,32 +532,15 @@ def compare(lhs, rhs, operator):
505532
"The date provided must be a string or date object"
506533
)
507534

508-
if operator in (
509-
"semver_eq",
510-
"semver_neq",
511-
"semver_gt",
512-
"semver_gte",
513-
"semver_lt",
514-
"semver_lte",
515-
"semver_tilde",
516-
"semver_caret",
517-
"semver_wildcard",
518-
):
535+
if operator in SEMVER_OPERATORS:
519536
try:
520537
override_parsed = parse_semver(override_value)
521538
except (ValueError, TypeError):
522539
raise InconclusiveMatchError(
523540
f"Person property value '{override_value}' is not a valid semver"
524541
)
525542

526-
if operator in (
527-
"semver_eq",
528-
"semver_neq",
529-
"semver_gt",
530-
"semver_gte",
531-
"semver_lt",
532-
"semver_lte",
533-
):
543+
if operator in SEMVER_COMPARISON_OPERATORS:
534544
try:
535545
flag_parsed = parse_semver(value)
536546
except (ValueError, TypeError):
@@ -578,7 +588,8 @@ def compare(lhs, rhs, operator):
578588
)
579589
return lower <= override_parsed < upper
580590

581-
# if we get here, we don't know how to handle the operator
591+
# Unreachable: all operators in PROPERTY_OPERATORS are handled above,
592+
# and unknown operators are rejected at the top of this function.
582593
raise InconclusiveMatchError(f"Unknown operator {operator}")
583594

584595

0 commit comments

Comments
 (0)