posthoganalytics 6.7.8__py3-none-any.whl → 6.7.10__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- posthoganalytics/__init__.py +1 -0
- posthoganalytics/client.py +6 -2
- posthoganalytics/feature_flags.py +25 -2
- posthoganalytics/integrations/django.py +40 -38
- posthoganalytics/test/test_feature_flags.py +123 -0
- posthoganalytics/version.py +1 -1
- {posthoganalytics-6.7.8.dist-info → posthoganalytics-6.7.10.dist-info}/METADATA +1 -1
- {posthoganalytics-6.7.8.dist-info → posthoganalytics-6.7.10.dist-info}/RECORD +11 -11
- {posthoganalytics-6.7.8.dist-info → posthoganalytics-6.7.10.dist-info}/WHEEL +0 -0
- {posthoganalytics-6.7.8.dist-info → posthoganalytics-6.7.10.dist-info}/licenses/LICENSE +0 -0
- {posthoganalytics-6.7.8.dist-info → posthoganalytics-6.7.10.dist-info}/top_level.txt +0 -0
posthoganalytics/__init__.py
CHANGED
|
@@ -11,6 +11,7 @@ from posthoganalytics.contexts import (
|
|
|
11
11
|
set_context_session as inner_set_context_session,
|
|
12
12
|
identify_context as inner_identify_context,
|
|
13
13
|
)
|
|
14
|
+
from posthoganalytics.feature_flags import InconclusiveMatchError, RequiresServerEvaluation
|
|
14
15
|
from posthoganalytics.types import FeatureFlag, FlagsAndPayloads, FeatureFlagResult
|
|
15
16
|
from posthoganalytics.version import VERSION
|
|
16
17
|
|
posthoganalytics/client.py
CHANGED
|
@@ -20,7 +20,11 @@ from posthoganalytics.exception_utils import (
|
|
|
20
20
|
exception_is_already_captured,
|
|
21
21
|
mark_exception_as_captured,
|
|
22
22
|
)
|
|
23
|
-
from posthoganalytics.feature_flags import
|
|
23
|
+
from posthoganalytics.feature_flags import (
|
|
24
|
+
InconclusiveMatchError,
|
|
25
|
+
RequiresServerEvaluation,
|
|
26
|
+
match_feature_flag_properties,
|
|
27
|
+
)
|
|
24
28
|
from posthoganalytics.poller import Poller
|
|
25
29
|
from posthoganalytics.request import (
|
|
26
30
|
DEFAULT_HOST,
|
|
@@ -1583,7 +1587,7 @@ class Client(object):
|
|
|
1583
1587
|
self.log.debug(
|
|
1584
1588
|
f"Successfully computed flag locally: {key} -> {response}"
|
|
1585
1589
|
)
|
|
1586
|
-
except InconclusiveMatchError as e:
|
|
1590
|
+
except (RequiresServerEvaluation, InconclusiveMatchError) as e:
|
|
1587
1591
|
self.log.debug(f"Failed to compute flag {key} locally: {e}")
|
|
1588
1592
|
except Exception as e:
|
|
1589
1593
|
self.log.exception(
|
|
@@ -22,6 +22,18 @@ class InconclusiveMatchError(Exception):
|
|
|
22
22
|
pass
|
|
23
23
|
|
|
24
24
|
|
|
25
|
+
class RequiresServerEvaluation(Exception):
|
|
26
|
+
"""
|
|
27
|
+
Raised when feature flag evaluation requires server-side data that is not
|
|
28
|
+
available locally (e.g., static cohorts, experience continuity).
|
|
29
|
+
|
|
30
|
+
This error should propagate immediately to trigger API fallback, unlike
|
|
31
|
+
InconclusiveMatchError which allows trying other conditions.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
|
|
25
37
|
# This function takes a distinct_id and a feature flag key and returns a float between 0 and 1.
|
|
26
38
|
# Given the same distinct_id and key, it'll always return the same float. These floats are
|
|
27
39
|
# uniformly distributed between 0 and 1, so if we want to show this feature to 20% of traffic
|
|
@@ -239,7 +251,12 @@ def match_feature_flag_properties(
|
|
|
239
251
|
else:
|
|
240
252
|
variant = get_matching_variant(flag, distinct_id)
|
|
241
253
|
return variant or True
|
|
254
|
+
except RequiresServerEvaluation:
|
|
255
|
+
# Static cohort or other missing server-side data - must fallback to API
|
|
256
|
+
raise
|
|
242
257
|
except InconclusiveMatchError:
|
|
258
|
+
# Evaluation error (bad regex, invalid date, missing property, etc.)
|
|
259
|
+
# Track that we had an inconclusive match, but try other conditions
|
|
243
260
|
is_inconclusive = True
|
|
244
261
|
|
|
245
262
|
if is_inconclusive:
|
|
@@ -449,8 +466,8 @@ def match_cohort(
|
|
|
449
466
|
# }
|
|
450
467
|
cohort_id = str(property.get("value"))
|
|
451
468
|
if cohort_id not in cohort_properties:
|
|
452
|
-
raise
|
|
453
|
-
"
|
|
469
|
+
raise RequiresServerEvaluation(
|
|
470
|
+
f"cohort {cohort_id} not found in local cohorts - likely a static cohort that requires server evaluation"
|
|
454
471
|
)
|
|
455
472
|
|
|
456
473
|
property_group = cohort_properties[cohort_id]
|
|
@@ -503,6 +520,9 @@ def match_property_group(
|
|
|
503
520
|
# OR group
|
|
504
521
|
if matches:
|
|
505
522
|
return True
|
|
523
|
+
except RequiresServerEvaluation:
|
|
524
|
+
# Immediately propagate - this condition requires server-side data
|
|
525
|
+
raise
|
|
506
526
|
except InconclusiveMatchError as e:
|
|
507
527
|
log.debug(f"Failed to compute property {prop} locally: {e}")
|
|
508
528
|
error_matching_locally = True
|
|
@@ -552,6 +572,9 @@ def match_property_group(
|
|
|
552
572
|
return True
|
|
553
573
|
if not matches and negation:
|
|
554
574
|
return True
|
|
575
|
+
except RequiresServerEvaluation:
|
|
576
|
+
# Immediately propagate - this condition requires server-side data
|
|
577
|
+
raise
|
|
555
578
|
except InconclusiveMatchError as e:
|
|
556
579
|
log.debug(f"Failed to compute property {prop} locally: {e}")
|
|
557
580
|
error_matching_locally = True
|
|
@@ -3,13 +3,19 @@ from posthoganalytics import contexts
|
|
|
3
3
|
from posthoganalytics.client import Client
|
|
4
4
|
|
|
5
5
|
try:
|
|
6
|
-
from asgiref.sync import iscoroutinefunction
|
|
6
|
+
from asgiref.sync import iscoroutinefunction, markcoroutinefunction
|
|
7
7
|
except ImportError:
|
|
8
|
-
# Fallback for older Django versions
|
|
8
|
+
# Fallback for older Django versions without asgiref
|
|
9
9
|
import asyncio
|
|
10
10
|
|
|
11
11
|
iscoroutinefunction = asyncio.iscoroutinefunction
|
|
12
12
|
|
|
13
|
+
# No-op fallback for markcoroutinefunction
|
|
14
|
+
# Older Django versions without asgiref typically don't support async middleware anyway
|
|
15
|
+
def markcoroutinefunction(func):
|
|
16
|
+
return func
|
|
17
|
+
|
|
18
|
+
|
|
13
19
|
if TYPE_CHECKING:
|
|
14
20
|
from django.http import HttpRequest, HttpResponse # noqa: F401
|
|
15
21
|
from typing import Callable, Dict, Any, Optional, Union, Awaitable # noqa: F401
|
|
@@ -39,26 +45,24 @@ class PosthogContextMiddleware:
|
|
|
39
45
|
See the context documentation for more information. The extracted distinct ID and session ID, if found, are used to
|
|
40
46
|
associate all events captured in the middleware context with the same distinct ID and session as currently active on the
|
|
41
47
|
frontend. See the documentation for `set_context_session` and `identify_context` for more details.
|
|
48
|
+
|
|
49
|
+
This middleware is hybrid-capable: it supports both WSGI (sync) and ASGI (async) Django applications. The middleware
|
|
50
|
+
detects at initialization whether the next middleware in the chain is async or sync, and adapts its behavior accordingly.
|
|
51
|
+
This ensures compatibility with both pure sync and pure async middleware chains, as well as mixed chains in ASGI mode.
|
|
42
52
|
"""
|
|
43
53
|
|
|
44
|
-
# Django middleware capability flags
|
|
45
54
|
sync_capable = True
|
|
46
55
|
async_capable = True
|
|
47
56
|
|
|
48
57
|
def __init__(self, get_response):
|
|
49
58
|
# type: (Union[Callable[[HttpRequest], HttpResponse], Callable[[HttpRequest], Awaitable[HttpResponse]]]) -> None
|
|
59
|
+
self.get_response = get_response
|
|
50
60
|
self._is_coroutine = iscoroutinefunction(get_response)
|
|
51
|
-
self._async_get_response = None # type: Optional[Callable[[HttpRequest], Awaitable[HttpResponse]]]
|
|
52
|
-
self._sync_get_response = None # type: Optional[Callable[[HttpRequest], HttpResponse]]
|
|
53
61
|
|
|
62
|
+
# Mark this instance as a coroutine function if get_response is async
|
|
63
|
+
# This is required for Django to correctly detect async middleware
|
|
54
64
|
if self._is_coroutine:
|
|
55
|
-
self
|
|
56
|
-
"Callable[[HttpRequest], Awaitable[HttpResponse]]", get_response
|
|
57
|
-
)
|
|
58
|
-
else:
|
|
59
|
-
self._sync_get_response = cast(
|
|
60
|
-
"Callable[[HttpRequest], HttpResponse]", get_response
|
|
61
|
-
)
|
|
65
|
+
markcoroutinefunction(self)
|
|
62
66
|
|
|
63
67
|
from django.conf import settings
|
|
64
68
|
|
|
@@ -181,40 +185,38 @@ class PosthogContextMiddleware:
|
|
|
181
185
|
return user_id, email
|
|
182
186
|
|
|
183
187
|
def __call__(self, request):
|
|
184
|
-
# type: (HttpRequest) -> HttpResponse
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
if self._is_coroutine:
|
|
188
|
-
raise RuntimeError(
|
|
189
|
-
"PosthogContextMiddleware received sync call but get_response is async"
|
|
190
|
-
)
|
|
188
|
+
# type: (HttpRequest) -> Union[HttpResponse, Awaitable[HttpResponse]]
|
|
189
|
+
"""
|
|
190
|
+
Unified entry point for both sync and async request handling.
|
|
191
191
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
192
|
+
When sync_capable and async_capable are both True, Django passes requests
|
|
193
|
+
without conversion. This method detects the mode and routes accordingly.
|
|
194
|
+
"""
|
|
195
|
+
if self._is_coroutine:
|
|
196
|
+
return self.__acall__(request)
|
|
197
|
+
else:
|
|
198
|
+
# Synchronous path
|
|
199
|
+
if self.request_filter and not self.request_filter(request):
|
|
200
|
+
return self.get_response(request)
|
|
195
201
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
202
|
+
with contexts.new_context(self.capture_exceptions, client=self.client):
|
|
203
|
+
for k, v in self.extract_tags(request).items():
|
|
204
|
+
contexts.tag(k, v)
|
|
199
205
|
|
|
200
|
-
|
|
201
|
-
return self._sync_get_response(request)
|
|
206
|
+
return self.get_response(request)
|
|
202
207
|
|
|
203
208
|
async def __acall__(self, request):
|
|
204
|
-
# type: (HttpRequest) -> HttpResponse
|
|
209
|
+
# type: (HttpRequest) -> Awaitable[HttpResponse]
|
|
210
|
+
"""
|
|
211
|
+
Asynchronous entry point for async request handling.
|
|
212
|
+
|
|
213
|
+
This method is called when the middleware chain is async.
|
|
214
|
+
"""
|
|
205
215
|
if self.request_filter and not self.request_filter(request):
|
|
206
|
-
|
|
207
|
-
return await self._async_get_response(request)
|
|
208
|
-
else:
|
|
209
|
-
assert self._sync_get_response is not None
|
|
210
|
-
return self._sync_get_response(request)
|
|
216
|
+
return await self.get_response(request)
|
|
211
217
|
|
|
212
218
|
with contexts.new_context(self.capture_exceptions, client=self.client):
|
|
213
219
|
for k, v in self.extract_tags(request).items():
|
|
214
220
|
contexts.tag(k, v)
|
|
215
221
|
|
|
216
|
-
|
|
217
|
-
return await self._async_get_response(request)
|
|
218
|
-
else:
|
|
219
|
-
assert self._sync_get_response is not None
|
|
220
|
-
return self._sync_get_response(request)
|
|
222
|
+
return await self.get_response(request)
|
|
@@ -3013,6 +3013,75 @@ class TestLocalEvaluation(unittest.TestCase):
|
|
|
3013
3013
|
)
|
|
3014
3014
|
self.assertEqual(patch_flags.call_count, 0)
|
|
3015
3015
|
|
|
3016
|
+
@mock.patch("posthog.client.flags")
|
|
3017
|
+
@mock.patch("posthog.client.get")
|
|
3018
|
+
def test_fallback_to_api_when_flag_has_static_cohort_in_multi_condition(
|
|
3019
|
+
self, patch_get, patch_flags
|
|
3020
|
+
):
|
|
3021
|
+
"""
|
|
3022
|
+
When a flag has multiple conditions and one contains a static cohort,
|
|
3023
|
+
the SDK should fallback to API for the entire flag, not just skip that
|
|
3024
|
+
condition and evaluate the next one locally.
|
|
3025
|
+
|
|
3026
|
+
This prevents returning wrong variants when later conditions could match
|
|
3027
|
+
locally but the user is actually in the static cohort.
|
|
3028
|
+
"""
|
|
3029
|
+
client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY)
|
|
3030
|
+
|
|
3031
|
+
# Mock the local flags response - cohort 999 is NOT in cohorts map (static cohort)
|
|
3032
|
+
client.feature_flags = [
|
|
3033
|
+
{
|
|
3034
|
+
"id": 1,
|
|
3035
|
+
"key": "multi-condition-flag",
|
|
3036
|
+
"active": True,
|
|
3037
|
+
"filters": {
|
|
3038
|
+
"groups": [
|
|
3039
|
+
{
|
|
3040
|
+
"properties": [
|
|
3041
|
+
{"key": "id", "value": 999, "type": "cohort"}
|
|
3042
|
+
],
|
|
3043
|
+
"rollout_percentage": 100,
|
|
3044
|
+
"variant": "set-1",
|
|
3045
|
+
},
|
|
3046
|
+
{
|
|
3047
|
+
"properties": [
|
|
3048
|
+
{
|
|
3049
|
+
"key": "$geoip_country_code",
|
|
3050
|
+
"operator": "exact",
|
|
3051
|
+
"value": ["DE"],
|
|
3052
|
+
"type": "person",
|
|
3053
|
+
}
|
|
3054
|
+
],
|
|
3055
|
+
"rollout_percentage": 100,
|
|
3056
|
+
"variant": "set-8",
|
|
3057
|
+
},
|
|
3058
|
+
],
|
|
3059
|
+
"multivariate": {
|
|
3060
|
+
"variants": [
|
|
3061
|
+
{"key": "set-1", "rollout_percentage": 50},
|
|
3062
|
+
{"key": "set-8", "rollout_percentage": 50},
|
|
3063
|
+
]
|
|
3064
|
+
},
|
|
3065
|
+
},
|
|
3066
|
+
}
|
|
3067
|
+
]
|
|
3068
|
+
client.cohorts = {} # Note: cohort 999 is NOT here - it's a static cohort
|
|
3069
|
+
|
|
3070
|
+
# Mock the API response - user is in the static cohort
|
|
3071
|
+
patch_flags.return_value = {"featureFlags": {"multi-condition-flag": "set-1"}}
|
|
3072
|
+
|
|
3073
|
+
result = client.get_feature_flag(
|
|
3074
|
+
"multi-condition-flag",
|
|
3075
|
+
"test-distinct-id",
|
|
3076
|
+
person_properties={"$geoip_country_code": "DE"},
|
|
3077
|
+
)
|
|
3078
|
+
|
|
3079
|
+
# Should return the API result (set-1), not local evaluation (set-8)
|
|
3080
|
+
self.assertEqual(result, "set-1")
|
|
3081
|
+
|
|
3082
|
+
# Verify API was called (fallback occurred)
|
|
3083
|
+
self.assertEqual(patch_flags.call_count, 1)
|
|
3084
|
+
|
|
3016
3085
|
|
|
3017
3086
|
class TestMatchProperties(unittest.TestCase):
|
|
3018
3087
|
def property(self, key, value, operator=None):
|
|
@@ -4006,6 +4075,60 @@ class TestCaptureCalls(unittest.TestCase):
|
|
|
4006
4075
|
|
|
4007
4076
|
patch_capture.reset_mock()
|
|
4008
4077
|
|
|
4078
|
+
@mock.patch("posthog.client.flags")
|
|
4079
|
+
def test_fallback_to_api_in_get_feature_flag_payload_when_flag_has_static_cohort(
|
|
4080
|
+
self, patch_flags
|
|
4081
|
+
):
|
|
4082
|
+
"""
|
|
4083
|
+
Test that get_feature_flag_payload falls back to API when evaluating
|
|
4084
|
+
a flag with static cohorts, similar to get_feature_flag behavior.
|
|
4085
|
+
"""
|
|
4086
|
+
client = Client(FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY)
|
|
4087
|
+
|
|
4088
|
+
# Mock the local flags response - cohort 999 is NOT in cohorts map (static cohort)
|
|
4089
|
+
client.feature_flags = [
|
|
4090
|
+
{
|
|
4091
|
+
"id": 1,
|
|
4092
|
+
"name": "Multi-condition Flag",
|
|
4093
|
+
"key": "multi-condition-flag",
|
|
4094
|
+
"active": True,
|
|
4095
|
+
"filters": {
|
|
4096
|
+
"groups": [
|
|
4097
|
+
{
|
|
4098
|
+
"properties": [
|
|
4099
|
+
{"key": "id", "value": 999, "type": "cohort"}
|
|
4100
|
+
],
|
|
4101
|
+
"rollout_percentage": 100,
|
|
4102
|
+
"variant": "variant-1",
|
|
4103
|
+
}
|
|
4104
|
+
],
|
|
4105
|
+
"multivariate": {
|
|
4106
|
+
"variants": [{"key": "variant-1", "rollout_percentage": 100}]
|
|
4107
|
+
},
|
|
4108
|
+
"payloads": {"variant-1": '{"message": "local-payload"}'},
|
|
4109
|
+
},
|
|
4110
|
+
}
|
|
4111
|
+
]
|
|
4112
|
+
client.cohorts = {} # Note: cohort 999 is NOT here - it's a static cohort
|
|
4113
|
+
|
|
4114
|
+
# Mock the API response - user is in the static cohort
|
|
4115
|
+
patch_flags.return_value = {
|
|
4116
|
+
"featureFlags": {"multi-condition-flag": "variant-1"},
|
|
4117
|
+
"featureFlagPayloads": {"multi-condition-flag": '{"message": "from-api"}'},
|
|
4118
|
+
}
|
|
4119
|
+
|
|
4120
|
+
# Call get_feature_flag_payload without match_value to trigger evaluation
|
|
4121
|
+
result = client.get_feature_flag_payload(
|
|
4122
|
+
"multi-condition-flag",
|
|
4123
|
+
"test-distinct-id",
|
|
4124
|
+
)
|
|
4125
|
+
|
|
4126
|
+
# Should return the API payload, not local payload
|
|
4127
|
+
self.assertEqual(result, {"message": "from-api"})
|
|
4128
|
+
|
|
4129
|
+
# Verify API was called (fallback occurred)
|
|
4130
|
+
self.assertEqual(patch_flags.call_count, 1)
|
|
4131
|
+
|
|
4009
4132
|
@mock.patch.object(Client, "capture")
|
|
4010
4133
|
@mock.patch("posthog.client.flags")
|
|
4011
4134
|
def test_disable_geoip_get_flag_capture_call(self, patch_flags, patch_capture):
|
posthoganalytics/version.py
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
posthoganalytics/__init__.py,sha256=
|
|
1
|
+
posthoganalytics/__init__.py,sha256=vYBBQuWxyCdN2mkFuJgHqGGh0ZcO7WriFy7tEILtpSI,26079
|
|
2
2
|
posthoganalytics/args.py,sha256=iZ2JWeANiAREJKhS-Qls9tIngjJOSfAVR8C4xFT5sHw,3307
|
|
3
|
-
posthoganalytics/client.py,sha256=
|
|
3
|
+
posthoganalytics/client.py,sha256=43gscUKk6GFqBXRrlodxR9ZnTuNlChAPxYe3UW7wqps,72759
|
|
4
4
|
posthoganalytics/consumer.py,sha256=CiNbJBdyW9jER3ZYCKbX-JFmEDXlE1lbDy1MSl43-a0,4617
|
|
5
5
|
posthoganalytics/contexts.py,sha256=LFSFIYpUFWKTBnGMjV9n1aYHWbAzz5zLJGr2qG34PoE,9405
|
|
6
6
|
posthoganalytics/exception_capture.py,sha256=1VHBfffrXXrkK0PT8iVgKPpj_R1pGAzG5f3Qw0WF79w,1783
|
|
7
7
|
posthoganalytics/exception_utils.py,sha256=P_75873Y2jayqlLiIkbxCNE7Bc8cM6J9kfrdZ5ZSnA0,26696
|
|
8
|
-
posthoganalytics/feature_flags.py,sha256=
|
|
8
|
+
posthoganalytics/feature_flags.py,sha256=yHjiH6LSvhQgurbsPCHUdGakZKvkzOLdqB8vL3iyhmw,22544
|
|
9
9
|
posthoganalytics/poller.py,sha256=jBz5rfH_kn_bBz7wCB46Fpvso4ttx4uzqIZWvXBCFmQ,595
|
|
10
10
|
posthoganalytics/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
11
|
posthoganalytics/request.py,sha256=Bsl2c5WwONKPQzwWMmKPX5VgOlwSiIcSNfhXgoz62Y8,6186
|
|
12
12
|
posthoganalytics/types.py,sha256=Dl3aFGX9XUR0wMmK12r2s5Hjan9jL4HpQ9GHpVcEq5U,10207
|
|
13
13
|
posthoganalytics/utils.py,sha256=-0w-OLcCaoldkbBebPzQyBzLJSo9G9yBOg8NDVz7La8,16088
|
|
14
|
-
posthoganalytics/version.py,sha256=
|
|
14
|
+
posthoganalytics/version.py,sha256=nZjqu-65D7o-byHYYEIUo6_Aa-szIb7lGhSsPoJkJas,88
|
|
15
15
|
posthoganalytics/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
16
|
posthoganalytics/ai/sanitization.py,sha256=owipZ4eJYtd4JTI-CM_klatclXaeaIec3XJBOUfsOnQ,5770
|
|
17
17
|
posthoganalytics/ai/types.py,sha256=ceubs4K9xf8vQx7wokq1NL9hPtxyS7D7sUOuT7Lx1lM,3237
|
|
@@ -32,7 +32,7 @@ posthoganalytics/ai/openai/openai_async.py,sha256=YAaj8Q-X3bExx-BXLWUOtdTMdj3RKe
|
|
|
32
32
|
posthoganalytics/ai/openai/openai_converter.py,sha256=VBaAGdXPSVNgfvCnSAojslWkTRO2luUxpjafR-WMEbs,20469
|
|
33
33
|
posthoganalytics/ai/openai/openai_providers.py,sha256=RPVmj2V0_lAdno_ax5Ul2kwhBA9_rRgAdl_sCqrQc6M,4004
|
|
34
34
|
posthoganalytics/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
35
|
-
posthoganalytics/integrations/django.py,sha256=
|
|
35
|
+
posthoganalytics/integrations/django.py,sha256=R9xBkd1BaeXICocHESevosDTiwxcgsajjEQKfiOx88Q,8685
|
|
36
36
|
posthoganalytics/test/__init__.py,sha256=VYgM6xPbJbvS-xhIcDiBRs0MFC9V_jT65uNeerCz_rM,299
|
|
37
37
|
posthoganalytics/test/test_before_send.py,sha256=A1_UVMewhHAvO39rZDWfS606vG_X-q0KNXvh5DAKiB8,7930
|
|
38
38
|
posthoganalytics/test/test_client.py,sha256=e1dD9bFplZWROiP35fuyBDguGXC6ZmG5j79Iw2t_NBw,96363
|
|
@@ -41,14 +41,14 @@ posthoganalytics/test/test_contexts.py,sha256=c--hNUIEf6SHQ7H9vdPhU1oLCN0SnD4wDb
|
|
|
41
41
|
posthoganalytics/test/test_exception_capture.py,sha256=al37Kg6wjzL_IBCFUUXRvkP6nVrqS6IZRCOKSo29Nh8,1063
|
|
42
42
|
posthoganalytics/test/test_feature_flag.py,sha256=9RQwB5eCvVAGrrO7UnR3Z1OidP_YoL4iBl3A83fuAig,6824
|
|
43
43
|
posthoganalytics/test/test_feature_flag_result.py,sha256=z2OgD97r85LKMqCnoCqAs74WjUMucayAtC3qWaITGCA,15898
|
|
44
|
-
posthoganalytics/test/test_feature_flags.py,sha256=
|
|
44
|
+
posthoganalytics/test/test_feature_flags.py,sha256=b0CcW2JyBRhOxlWX9KOBncqL7OG_VHFX3Z4J6VOlPNs,217352
|
|
45
45
|
posthoganalytics/test/test_module.py,sha256=M772XKYO30XluqBTumZFFnYGqVxDmKKly4eUjhLIjZU,822
|
|
46
46
|
posthoganalytics/test/test_request.py,sha256=Zc0VbkjpVmj8mKokQm9rzdgTr0b1U44vvMYSkB_IQLs,4467
|
|
47
47
|
posthoganalytics/test/test_size_limited_dict.py,sha256=-5IQjIEr_-Dql24M0HusdR_XroOMrtgiT0v6ZQCRvzo,774
|
|
48
48
|
posthoganalytics/test/test_types.py,sha256=bRPHdwVpP7hu7emsplU8UVyzSQptv6PaG5lAoOD_BtM,7595
|
|
49
49
|
posthoganalytics/test/test_utils.py,sha256=sqUTbfweVcxxFRd3WDMFXqPMyU6DvzOBeAOc68Py9aw,9620
|
|
50
|
-
posthoganalytics-6.7.
|
|
51
|
-
posthoganalytics-6.7.
|
|
52
|
-
posthoganalytics-6.7.
|
|
53
|
-
posthoganalytics-6.7.
|
|
54
|
-
posthoganalytics-6.7.
|
|
50
|
+
posthoganalytics-6.7.10.dist-info/licenses/LICENSE,sha256=wGf9JBotDkSygFj43m49oiKlFnpMnn97keiZKF-40vE,2450
|
|
51
|
+
posthoganalytics-6.7.10.dist-info/METADATA,sha256=Um6xU5CvpWKIitESp-z-aseQ94A2kvZjWuhFP4H61lo,6025
|
|
52
|
+
posthoganalytics-6.7.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
53
|
+
posthoganalytics-6.7.10.dist-info/top_level.txt,sha256=8QsNIqIkBh1p2TXvKp0Em9ZLZKwe3uIqCETyW4s1GOE,17
|
|
54
|
+
posthoganalytics-6.7.10.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|