11import logging
22import typing
3+ from dataclasses import dataclass
34
45import mmh3
56import semver
1011logger = logging .getLogger ("openfeature.contrib" )
1112
1213
14+ @dataclass
15+ class Fraction :
16+ variant : str
17+ weight : int = 1
18+
19+
1320def fractional (data : dict , * args : JsonLogicArg ) -> typing .Optional [str ]:
1421 if not args :
1522 logger .error ("No arguments provided to fractional operator." )
@@ -32,28 +39,51 @@ def fractional(data: dict, *args: JsonLogicArg) -> typing.Optional[str]:
3239 return None
3340
3441 hash_ratio = abs (mmh3 .hash (bucket_by )) / (2 ** 31 - 1 )
35- bucket = int ( hash_ratio * 100 )
42+ bucket = hash_ratio * 100
3643
44+ total_weight = 0
45+ fractions = []
3746 for arg in args :
38- if (
39- not isinstance (arg , (tuple , list ))
40- or len (arg ) != 2
41- or not isinstance (arg [0 ], str )
42- or not isinstance (arg [1 ], int )
43- ):
44- logger .error ("Fractional variant weights must be (str, int) tuple" )
45- return None
46- variant_weights : typing .Tuple [typing .Tuple [str , int ]] = args # type: ignore[assignment]
47-
48- range_end = 0
49- for variant , weight in variant_weights :
50- range_end += weight
47+ fraction = _parse_fraction (arg )
48+ if fraction :
49+ fractions .append (fraction )
50+ total_weight += fraction .weight
51+
52+ range_end : float = 0
53+ for fraction in fractions :
54+ range_end += fraction .weight * 100 / total_weight
5155 if bucket < range_end :
52- return variant
56+ return fraction . variant
5357
5458 return None
5559
5660
61+ def _parse_fraction (arg : JsonLogicArg ) -> typing .Optional [Fraction ]:
62+ if not isinstance (arg , (tuple , list )) or not arg :
63+ logger .error (
64+ "Fractional variant weights must be (str, int) tuple or [str] list"
65+ )
66+ return None
67+
68+ if not isinstance (arg [0 ], str ):
69+ logger .error (
70+ "Fractional variant identifier (first element) isn't of type 'str'"
71+ )
72+ return None
73+
74+ if len (arg ) >= 2 and not isinstance (arg [1 ], int ):
75+ logger .error (
76+ "Fractional variant weight value (second element) isn't of type 'int'"
77+ )
78+ return None
79+
80+ fraction = Fraction (variant = arg [0 ])
81+ if len (arg ) >= 2 :
82+ fraction .weight = arg [1 ]
83+
84+ return fraction
85+
86+
5787def starts_with (data : dict , * args : JsonLogicArg ) -> typing .Optional [bool ]:
5888 def f (s1 : str , s2 : str ) -> bool :
5989 return s1 .startswith (s2 )
0 commit comments