posthoganalytics 6.0.3__py3-none-any.whl → 6.1.0__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.
@@ -60,6 +60,9 @@ log_captured_exceptions = False # type: bool
60
60
  project_root = None # type: Optional[str]
61
61
  # Used for our AI observability feature to not capture any prompt or output just usage + metadata
62
62
  privacy_mode = False # type: bool
63
+ # Whether to enable feature flag polling for local evaluation by default. Defaults to True.
64
+ # We recommend setting this to False if you are only using the personalApiKey for evaluating remote config payloads via `get_remote_config_payload` and not using local evaluation.
65
+ enable_local_evaluation = True # type: bool
63
66
 
64
67
  default_client = None # type: Optional[Client]
65
68
 
@@ -465,6 +468,7 @@ def setup():
465
468
  # or deprecate this proxy option fully (it's already in the process of deprecation, no new clients should be using this method since like 5-6 months)
466
469
  enable_exception_autocapture=enable_exception_autocapture,
467
470
  log_captured_exceptions=log_captured_exceptions,
471
+ enable_local_evaluation=enable_local_evaluation,
468
472
  )
469
473
 
470
474
  # always set incase user changes it
@@ -152,6 +152,7 @@ class Client(object):
152
152
  privacy_mode=False,
153
153
  before_send=None,
154
154
  flag_fallback_cache_url=None,
155
+ enable_local_evaluation=True,
155
156
  ):
156
157
  self.queue = queue.Queue(max_queue_size)
157
158
 
@@ -187,6 +188,7 @@ class Client(object):
187
188
  self.log_captured_exceptions = log_captured_exceptions
188
189
  self.exception_capture = None
189
190
  self.privacy_mode = privacy_mode
191
+ self.enable_local_evaluation = enable_local_evaluation
190
192
 
191
193
  if project_root is None:
192
194
  try:
@@ -807,7 +809,11 @@ class Client(object):
807
809
  return
808
810
 
809
811
  self._load_feature_flags()
810
- if not (self.poller and self.poller.is_alive()):
812
+
813
+ # Only start the poller if local evaluation is enabled
814
+ if self.enable_local_evaluation and not (
815
+ self.poller and self.poller.is_alive()
816
+ ):
811
817
  self.poller = Poller(
812
818
  interval=timedelta(seconds=self.poll_interval),
813
819
  execute=self._load_feature_flags,
@@ -1,5 +1,6 @@
1
1
  from typing import TYPE_CHECKING, cast
2
2
  from posthoganalytics import contexts
3
+ from posthoganalytics.client import Client
3
4
 
4
5
  if TYPE_CHECKING:
5
6
  from django.http import HttpRequest, HttpResponse # noqa: F401
@@ -16,7 +17,8 @@ class PosthogContextMiddleware:
16
17
  - Request Method as $request_method
17
18
 
18
19
  The context will also auto-capture exceptions and send them to PostHog, unless you disable it by setting
19
- `POSTHOG_MW_CAPTURE_EXCEPTIONS` to `False` in your Django settings.
20
+ `POSTHOG_MW_CAPTURE_EXCEPTIONS` to `False` in your Django settings. The exceptions are captured using the
21
+ global client, unless the setting `POSTHOG_MW_CLIENT` is set to a custom client instance
20
22
 
21
23
  The middleware behaviour is customisable through 3 additional functions:
22
24
  - `POSTHOG_MW_EXTRA_TAGS`, which is a Callable[[HttpRequest], Dict[str, Any]] expected to return a dictionary of additional tags to be added to the context.
@@ -74,6 +76,13 @@ class PosthogContextMiddleware:
74
76
  else:
75
77
  self.capture_exceptions = True
76
78
 
79
+ if hasattr(settings, "POSTHOG_MW_CLIENT") and isinstance(
80
+ settings.POSTHOG_MW_CLIENT, Client
81
+ ):
82
+ self.client = cast("Optional[Client]", settings.POSTHOG_MW_CLIENT)
83
+ else:
84
+ self.client = None
85
+
77
86
  def extract_tags(self, request):
78
87
  # type: (HttpRequest) -> Dict[str, Any]
79
88
  tags = {}
@@ -153,7 +162,7 @@ class PosthogContextMiddleware:
153
162
  if self.request_filter and not self.request_filter(request):
154
163
  return self.get_response(request)
155
164
 
156
- with contexts.new_context(self.capture_exceptions):
165
+ with contexts.new_context(self.capture_exceptions, client=self.client):
157
166
  for k, v in self.extract_tags(request).items():
158
167
  contexts.tag(k, v)
159
168
 
@@ -1903,3 +1903,93 @@ class TestClient(unittest.TestCase):
1903
1903
  self.assertEqual(
1904
1904
  msg["properties"]["$session_id"], "explicit-session-override"
1905
1905
  )
1906
+
1907
+ @mock.patch("posthog.client.Poller")
1908
+ @mock.patch("posthog.client.get")
1909
+ def test_enable_local_evaluation_false_disables_poller(
1910
+ self, patch_get, patch_poller
1911
+ ):
1912
+ """Test that when enable_local_evaluation=False, the poller is not started"""
1913
+ patch_get.return_value = {
1914
+ "flags": [
1915
+ {"id": 1, "name": "Beta Feature", "key": "beta-feature", "active": True}
1916
+ ],
1917
+ "group_type_mapping": {},
1918
+ "cohorts": {},
1919
+ }
1920
+
1921
+ client = Client(
1922
+ FAKE_TEST_API_KEY,
1923
+ personal_api_key="test-personal-key",
1924
+ enable_local_evaluation=False,
1925
+ )
1926
+
1927
+ # Load feature flags should not start the poller
1928
+ client.load_feature_flags()
1929
+
1930
+ # Assert that the poller was not created/started
1931
+ patch_poller.assert_not_called()
1932
+ # But the feature flags should still be loaded
1933
+ patch_get.assert_called_once()
1934
+ self.assertEqual(len(client.feature_flags), 1)
1935
+ self.assertEqual(client.feature_flags[0]["key"], "beta-feature")
1936
+
1937
+ @mock.patch("posthog.client.Poller")
1938
+ @mock.patch("posthog.client.get")
1939
+ def test_enable_local_evaluation_true_starts_poller(self, patch_get, patch_poller):
1940
+ """Test that when enable_local_evaluation=True (default), the poller is started"""
1941
+ patch_get.return_value = {
1942
+ "flags": [
1943
+ {"id": 1, "name": "Beta Feature", "key": "beta-feature", "active": True}
1944
+ ],
1945
+ "group_type_mapping": {},
1946
+ "cohorts": {},
1947
+ }
1948
+
1949
+ client = Client(
1950
+ FAKE_TEST_API_KEY,
1951
+ personal_api_key="test-personal-key",
1952
+ enable_local_evaluation=True,
1953
+ )
1954
+
1955
+ # Load feature flags should start the poller
1956
+ client.load_feature_flags()
1957
+
1958
+ # Assert that the poller was created and started
1959
+ patch_poller.assert_called_once()
1960
+ patch_get.assert_called_once()
1961
+ self.assertEqual(len(client.feature_flags), 1)
1962
+ self.assertEqual(client.feature_flags[0]["key"], "beta-feature")
1963
+
1964
+ @mock.patch("posthog.client.remote_config")
1965
+ def test_get_remote_config_payload_works_without_poller(self, patch_remote_config):
1966
+ """Test that get_remote_config_payload works without local evaluation enabled"""
1967
+ patch_remote_config.return_value = {"test": "payload"}
1968
+
1969
+ client = Client(
1970
+ FAKE_TEST_API_KEY,
1971
+ personal_api_key="test-personal-key",
1972
+ enable_local_evaluation=False,
1973
+ )
1974
+
1975
+ # Should work without poller
1976
+ result = client.get_remote_config_payload("test-flag")
1977
+
1978
+ self.assertEqual(result, {"test": "payload"})
1979
+ patch_remote_config.assert_called_once_with(
1980
+ "test-personal-key",
1981
+ client.host,
1982
+ "test-flag",
1983
+ timeout=client.feature_flags_request_timeout_seconds,
1984
+ )
1985
+
1986
+ def test_get_remote_config_payload_requires_personal_api_key(self):
1987
+ """Test that get_remote_config_payload requires personal API key"""
1988
+ client = Client(
1989
+ FAKE_TEST_API_KEY,
1990
+ enable_local_evaluation=False,
1991
+ )
1992
+
1993
+ result = client.get_remote_config_payload("test-flag")
1994
+
1995
+ self.assertIsNone(result)
@@ -1,4 +1,4 @@
1
- VERSION = "6.0.3"
1
+ VERSION = "6.1.0"
2
2
 
3
3
  if __name__ == "__main__":
4
4
  print(VERSION, end="") # noqa: T201
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: posthoganalytics
3
- Version: 6.0.3
3
+ Version: 6.1.0
4
4
  Summary: Integrate PostHog into any python application.
5
5
  Home-page: https://github.com/posthog/posthog-python
6
6
  Author: Posthog
@@ -1,6 +1,6 @@
1
- posthoganalytics/__init__.py,sha256=kijo4odBr7QnGezx-o2yAzZlbg51sP4u6SXC8TADwr0,16692
1
+ posthoganalytics/__init__.py,sha256=U8EmLKYL0N-k-kPb-R0c36MXtaGkA7PmFnhek3Xga5k,17070
2
2
  posthoganalytics/args.py,sha256=hRKPQ3cPGyDn4S7Ay9t2NlgoZg1cJ0GeN_Mb6OKtmfo,3145
3
- posthoganalytics/client.py,sha256=mPUTTfz0hfIhUTdD1ngQ-JXtYJ_zIeTyhM08Dm0G480,50751
3
+ posthoganalytics/client.py,sha256=w2br3pz_1sV3IF39SZavukXowbDG8phrx9sHlltdgmc,50971
4
4
  posthoganalytics/consumer.py,sha256=CiNbJBdyW9jER3ZYCKbX-JFmEDXlE1lbDy1MSl43-a0,4617
5
5
  posthoganalytics/contexts.py,sha256=B3Y62sX7w-MCqNqgguUceQnKn5RCBFIqen3VeR3qems,9020
6
6
  posthoganalytics/exception_capture.py,sha256=1VHBfffrXXrkK0PT8iVgKPpj_R1pGAzG5f3Qw0WF79w,1783
@@ -11,7 +11,7 @@ posthoganalytics/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  posthoganalytics/request.py,sha256=TaeySYpcvHMf5Ftf5KqqlO0VPJpirKBCRrThlS04Kew,6124
12
12
  posthoganalytics/types.py,sha256=INxWBOEQc0xgPcap6FdQNSU7zuQBmKShYaGzyuHKql8,9128
13
13
  posthoganalytics/utils.py,sha256=-0w-OLcCaoldkbBebPzQyBzLJSo9G9yBOg8NDVz7La8,16088
14
- posthoganalytics/version.py,sha256=65DkL33wptG_S3mMCSglSwcvQWT2uqT8FwNKosP44_8,87
14
+ posthoganalytics/version.py,sha256=t8G8Y2aHP3Z3Ej7UE-_LT8EK7Gi6rurkCbuJWBqvhXE,87
15
15
  posthoganalytics/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  posthoganalytics/ai/utils.py,sha256=5-2XfmetCs0v9otBoux7-IEG933wAnKLSGS6oYLqCkw,19529
17
17
  posthoganalytics/ai/anthropic/__init__.py,sha256=fFhDOiRzTXzGQlgnrRDL-4yKC8EYIl8NW4a2QNR6xRU,368
@@ -27,10 +27,10 @@ posthoganalytics/ai/openai/openai.py,sha256=iL_cwctaAhPdXNo4EpIZooOWGyjNj0W-OUEo
27
27
  posthoganalytics/ai/openai/openai_async.py,sha256=KxPCd5imF5iZ9VkJ12HjCO2skaF1tHsHveAknIqV93g,23769
28
28
  posthoganalytics/ai/openai/openai_providers.py,sha256=EMuEvdHSOFbrhmfuU0is7pBVWS3ReAUT0PZqgMXdyjk,3884
29
29
  posthoganalytics/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
- posthoganalytics/integrations/django.py,sha256=NUC2XHZxQ3XmfqPaVKrAvVma4284eFqq-x0NQTRdaUU,5998
30
+ posthoganalytics/integrations/django.py,sha256=BdBo6nvY6cjKMYPpKTzvwiICCLIhCOyslgqwFCObPzo,6441
31
31
  posthoganalytics/test/__init__.py,sha256=VYgM6xPbJbvS-xhIcDiBRs0MFC9V_jT65uNeerCz_rM,299
32
32
  posthoganalytics/test/test_before_send.py,sha256=A1_UVMewhHAvO39rZDWfS606vG_X-q0KNXvh5DAKiB8,7930
33
- posthoganalytics/test/test_client.py,sha256=msR101AXAyDrglWMJ1MjSw59o6CsXSXnjZ0pwIG-MDk,74571
33
+ posthoganalytics/test/test_client.py,sha256=fghypNGUqOY9OmycsdNK5SsyCoimxYVisFfFq2nkTCU,77840
34
34
  posthoganalytics/test/test_consumer.py,sha256=HGMfU9PzQ5ZAe_R3kHnZNsMvD7jUjHL-gie0isrvMMk,7107
35
35
  posthoganalytics/test/test_contexts.py,sha256=c--hNUIEf6SHQ7H9vdPhU1oLCN0SnD4wDbFr-eLPHDo,7013
36
36
  posthoganalytics/test/test_exception_capture.py,sha256=al37Kg6wjzL_IBCFUUXRvkP6nVrqS6IZRCOKSo29Nh8,1063
@@ -42,8 +42,8 @@ posthoganalytics/test/test_request.py,sha256=Zc0VbkjpVmj8mKokQm9rzdgTr0b1U44vvMY
42
42
  posthoganalytics/test/test_size_limited_dict.py,sha256=-5IQjIEr_-Dql24M0HusdR_XroOMrtgiT0v6ZQCRvzo,774
43
43
  posthoganalytics/test/test_types.py,sha256=bRPHdwVpP7hu7emsplU8UVyzSQptv6PaG5lAoOD_BtM,7595
44
44
  posthoganalytics/test/test_utils.py,sha256=sqUTbfweVcxxFRd3WDMFXqPMyU6DvzOBeAOc68Py9aw,9620
45
- posthoganalytics-6.0.3.dist-info/licenses/LICENSE,sha256=wGf9JBotDkSygFj43m49oiKlFnpMnn97keiZKF-40vE,2450
46
- posthoganalytics-6.0.3.dist-info/METADATA,sha256=EPpHk0WvqHtRnzCqhAuyrivFQV9ftS3yIGFZMmJ3qfk,6024
47
- posthoganalytics-6.0.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
48
- posthoganalytics-6.0.3.dist-info/top_level.txt,sha256=8QsNIqIkBh1p2TXvKp0Em9ZLZKwe3uIqCETyW4s1GOE,17
49
- posthoganalytics-6.0.3.dist-info/RECORD,,
45
+ posthoganalytics-6.1.0.dist-info/licenses/LICENSE,sha256=wGf9JBotDkSygFj43m49oiKlFnpMnn97keiZKF-40vE,2450
46
+ posthoganalytics-6.1.0.dist-info/METADATA,sha256=xp_DkYzPEjNwHIZO2Ac3-Z-NDqBy5jv2nqrdzpfNvFU,6024
47
+ posthoganalytics-6.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
48
+ posthoganalytics-6.1.0.dist-info/top_level.txt,sha256=8QsNIqIkBh1p2TXvKp0Em9ZLZKwe3uIqCETyW4s1GOE,17
49
+ posthoganalytics-6.1.0.dist-info/RECORD,,