Skip to content

Commit a0c4fff

Browse files
authored
fix: improve strict public API types (#685)
* fix: improve strict public API types * address pr review feedback * ci: add strict type smoke test * address pr review feedback * address pr review feedback * address pr review feedback * clarify feature flag payload return type * align feature flag public types
1 parent a748308 commit a0c4fff

7 files changed

Lines changed: 320 additions & 210 deletions

File tree

.changeset/quiet-horses-type.md

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+
Improve strict Pyright coverage for public PostHog APIs.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
PYTHON_VERSION="${PYTHON_VERSION:-3.11}"
5+
tmp="$(mktemp -d)"
6+
trap 'rm -rf "$tmp"' EXIT
7+
8+
python -m venv "$tmp/.venv"
9+
"$tmp/.venv/bin/python" -m pip install --quiet --upgrade pip
10+
"$tmp/.venv/bin/python" -m pip install --quiet . pyright
11+
12+
cat > "$tmp/strict_posthog_types.py" <<'PY'
13+
# pyright: strict
14+
import atexit
15+
16+
import posthog
17+
from posthog import FeatureFlagEvaluations, FlagValue, Posthog
18+
19+
client = Posthog("phc_test")
20+
atexit.register(client.shutdown)
21+
22+
groups: dict[str, str | int] = {"company": 123}
23+
24+
flag_value: FlagValue | None = client.get_feature_flag("flag", 123, groups=groups)
25+
all_flags: dict[str, FlagValue] | None = posthog.get_all_flags("user", groups=groups)
26+
enabled: bool | None = posthog.feature_enabled("flag", "user", groups=groups)
27+
payload: object | None = client.get_feature_flag_payload("flag", "user", groups=groups)
28+
evaluations: FeatureFlagEvaluations = posthog.evaluate_flags(123, groups=groups)
29+
30+
_ = (flag_value, all_flags, enabled, payload, evaluations)
31+
PY
32+
33+
"$tmp/.venv/bin/python" - <<'PY' > "$tmp/public_api_access.py"
34+
import inspect
35+
36+
import posthog
37+
from posthog import Posthog
38+
39+
print("# pyright: strict")
40+
print("import posthog")
41+
print("from posthog import Posthog")
42+
print('client = Posthog("phc_test")')
43+
44+
for name, obj in inspect.getmembers(Posthog):
45+
if name.startswith("_"):
46+
continue
47+
if inspect.isfunction(obj) or inspect.ismethoddescriptor(obj):
48+
print(f"client_{name} = client.{name}")
49+
50+
for name, obj in inspect.getmembers(posthog):
51+
if name.startswith("_") or name.startswith("inner_"):
52+
continue
53+
if inspect.isfunction(obj):
54+
print(f"module_{name} = posthog.{name}")
55+
PY
56+
57+
cat > "$tmp/pyrightconfig.json" <<JSON
58+
{
59+
"typeCheckingMode": "strict",
60+
"pythonVersion": "$PYTHON_VERSION",
61+
"venvPath": "$tmp",
62+
"venv": ".venv",
63+
"reportMissingTypeStubs": "error",
64+
"reportPrivateImportUsage": "error",
65+
"reportUnknownArgumentType": "error",
66+
"reportUnknownMemberType": "error",
67+
"reportUnknownVariableType": "error"
68+
}
69+
JSON
70+
71+
cd "$tmp"
72+
"$tmp/.venv/bin/python" -m pyright strict_posthog_types.py public_api_access.py

.github/workflows/ci.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,23 @@ jobs:
7777
python .github/scripts/test_check_public_api.py
7878
make public_api_check
7979
80+
strict-type-smoke:
81+
name: Strict type smoke
82+
runs-on: ubuntu-latest
83+
84+
steps:
85+
- uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2
86+
with:
87+
fetch-depth: 1
88+
89+
- name: Set up Python 3.11
90+
uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55
91+
with:
92+
python-version: 3.11.11
93+
94+
- name: Check strict downstream Pyright usage
95+
run: .github/scripts/check_strict_types.sh
96+
8097
package-build:
8198
name: Package build
8299
runs-on: ubuntu-latest

posthog/__init__.py

Lines changed: 98 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import datetime # noqa: F401
2-
from typing import Any, Callable, Dict, Optional # noqa: F401
2+
from typing import Any, Callable, Dict, Mapping, Optional, Union # noqa: F401
33

44
from typing_extensions import Unpack
55

6-
from posthog.args import ExceptionArg, OptionalCaptureArgs, OptionalSetArgs
6+
from posthog.args import (
7+
ID_TYPES as ID_TYPES,
8+
ExceptionArg,
9+
OptionalCaptureArgs,
10+
OptionalSetArgs,
11+
)
712
from posthog.client import Client
813
from posthog.exception_capture import ExceptionCapture
914
from posthog.contexts import (
@@ -65,8 +70,9 @@
6570
)
6671
from posthog.types import (
6772
BeforeSendCallback as BeforeSendCallback,
68-
FeatureFlag,
69-
FlagsAndPayloads,
73+
FeatureFlag as FeatureFlag,
74+
FlagValue as FlagValue,
75+
FlagsAndPayloads as FlagsAndPayloads,
7076
)
7177
from posthog.types import (
7278
FeatureFlagResult as FeatureFlagResult,
@@ -202,7 +208,7 @@ def set_capture_exception_code_variables_context(enabled: bool):
202208
return inner_set_capture_exception_code_variables_context(enabled)
203209

204210

205-
def set_code_variables_mask_patterns_context(mask_patterns: list):
211+
def set_code_variables_mask_patterns_context(mask_patterns: list[str]):
206212
"""
207213
Override code-variable mask patterns for exceptions in the current context.
208214
@@ -216,7 +222,7 @@ def set_code_variables_mask_patterns_context(mask_patterns: list):
216222
return inner_set_code_variables_mask_patterns_context(mask_patterns)
217223

218224

219-
def set_code_variables_ignore_patterns_context(ignore_patterns: list):
225+
def set_code_variables_ignore_patterns_context(ignore_patterns: list[str]):
220226
"""
221227
Override code-variable ignore patterns for exceptions in the current context.
222228
@@ -510,15 +516,14 @@ def set_once(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str]:
510516

511517

512518
def group_identify(
513-
group_type, # type: str
514-
group_key, # type: str
515-
properties=None, # type: Optional[Dict]
516-
timestamp=None, # type: Optional[datetime.datetime]
517-
uuid=None, # type: Optional[str]
518-
disable_geoip=None, # type: Optional[bool]
519-
distinct_id=None, # type: Optional[str]
520-
):
521-
# type: (...) -> Optional[str]
519+
group_type: str,
520+
group_key: str,
521+
properties: Optional[Dict[str, Any]] = None,
522+
timestamp: Optional[datetime.datetime] = None,
523+
uuid: Optional[str] = None,
524+
disable_geoip: Optional[bool] = None,
525+
distinct_id: Optional[ID_TYPES] = None,
526+
) -> Optional[str]:
522527
"""
523528
Set properties on a group.
524529
@@ -557,13 +562,12 @@ def group_identify(
557562

558563

559564
def alias(
560-
previous_id, # type: str
561-
distinct_id, # type: str
562-
timestamp=None, # type: Optional[datetime.datetime]
563-
uuid=None, # type: Optional[str]
564-
disable_geoip=None, # type: Optional[bool]
565-
):
566-
# type: (...) -> Optional[str]
565+
previous_id: str,
566+
distinct_id: str,
567+
timestamp: Optional[datetime.datetime] = None,
568+
uuid: Optional[str] = None,
569+
disable_geoip: Optional[bool] = None,
570+
) -> Optional[str]:
567571
"""
568572
Associate user behaviour before and after they e.g. register, login, or perform some other identifying action.
569573
@@ -629,17 +633,16 @@ def capture_exception(
629633

630634

631635
def feature_enabled(
632-
key, # type: str
633-
distinct_id, # type: str
634-
groups=None, # type: Optional[dict]
635-
person_properties=None, # type: Optional[dict]
636-
group_properties=None, # type: Optional[dict]
637-
only_evaluate_locally=False, # type: bool
638-
send_feature_flag_events=True, # type: bool
639-
disable_geoip=None, # type: Optional[bool]
640-
device_id=None, # type: Optional[str]
641-
):
642-
# type: (...) -> bool
636+
key: str,
637+
distinct_id: ID_TYPES,
638+
groups: Optional[Mapping[str, Union[str, int]]] = None,
639+
person_properties: Optional[Dict[str, Any]] = None,
640+
group_properties: Optional[Dict[str, Dict[str, Any]]] = None,
641+
only_evaluate_locally: bool = False,
642+
send_feature_flag_events: bool = True,
643+
disable_geoip: Optional[bool] = None,
644+
device_id: Optional[str] = None,
645+
) -> Optional[bool]:
643646
"""
644647
Use feature flags to enable or disable features for users.
645648
@@ -683,16 +686,16 @@ def feature_enabled(
683686

684687

685688
def get_feature_flag(
686-
key, # type: str
687-
distinct_id, # type: str
688-
groups=None, # type: Optional[dict]
689-
person_properties=None, # type: Optional[dict]
690-
group_properties=None, # type: Optional[dict]
691-
only_evaluate_locally=False, # type: bool
692-
send_feature_flag_events=True, # type: bool
693-
disable_geoip=None, # type: Optional[bool]
694-
device_id=None, # type: Optional[str]
695-
) -> Optional[FeatureFlag]:
689+
key: str,
690+
distinct_id: ID_TYPES,
691+
groups: Optional[Mapping[str, Union[str, int]]] = None,
692+
person_properties: Optional[Dict[str, Any]] = None,
693+
group_properties: Optional[Dict[str, Dict[str, Any]]] = None,
694+
only_evaluate_locally: bool = False,
695+
send_feature_flag_events: bool = True,
696+
disable_geoip: Optional[bool] = None,
697+
device_id: Optional[str] = None,
698+
) -> Optional[FlagValue]:
696699
"""
697700
Get feature flag variant for users. Used with experiments.
698701
@@ -736,15 +739,15 @@ def get_feature_flag(
736739

737740

738741
def get_all_flags(
739-
distinct_id, # type: str
740-
groups=None, # type: Optional[dict]
741-
person_properties=None, # type: Optional[dict]
742-
group_properties=None, # type: Optional[dict]
743-
only_evaluate_locally=False, # type: bool
744-
disable_geoip=None, # type: Optional[bool]
745-
device_id=None, # type: Optional[str]
746-
flag_keys_to_evaluate=None, # type: Optional[list[str]]
747-
) -> Optional[dict[str, FeatureFlag]]:
742+
distinct_id: ID_TYPES,
743+
groups: Optional[Mapping[str, Union[str, int]]] = None,
744+
person_properties: Optional[Dict[str, Any]] = None,
745+
group_properties: Optional[Dict[str, Dict[str, Any]]] = None,
746+
only_evaluate_locally: bool = False,
747+
disable_geoip: Optional[bool] = None,
748+
device_id: Optional[str] = None,
749+
flag_keys_to_evaluate: Optional[list[str]] = None,
750+
) -> Optional[dict[str, FlagValue]]:
748751
"""
749752
Get all flags for a given user.
750753
@@ -784,17 +787,16 @@ def get_all_flags(
784787

785788

786789
def get_feature_flag_result(
787-
key,
788-
distinct_id,
789-
groups=None, # type: Optional[dict]
790-
person_properties=None, # type: Optional[dict]
791-
group_properties=None, # type: Optional[dict]
792-
only_evaluate_locally=False,
793-
send_feature_flag_events=True,
794-
disable_geoip=None, # type: Optional[bool]
795-
device_id=None, # type: Optional[str]
796-
):
797-
# type: (...) -> Optional[FeatureFlagResult]
790+
key: str,
791+
distinct_id: ID_TYPES,
792+
groups: Optional[Mapping[str, Union[str, int]]] = None,
793+
person_properties: Optional[Dict[str, Any]] = None,
794+
group_properties: Optional[Dict[str, Dict[str, Any]]] = None,
795+
only_evaluate_locally: bool = False,
796+
send_feature_flag_events: bool = True,
797+
disable_geoip: Optional[bool] = None,
798+
device_id: Optional[str] = None,
799+
) -> Optional[FeatureFlagResult]:
798800
"""
799801
Get a FeatureFlagResult object which contains the flag result and payload.
800802
@@ -840,17 +842,17 @@ def get_feature_flag_result(
840842

841843

842844
def get_feature_flag_payload(
843-
key,
844-
distinct_id,
845-
match_value=None,
846-
groups=None, # type: Optional[dict]
847-
person_properties=None, # type: Optional[dict]
848-
group_properties=None, # type: Optional[dict]
849-
only_evaluate_locally=False,
850-
send_feature_flag_events=True,
851-
disable_geoip=None, # type: Optional[bool]
852-
device_id=None, # type: Optional[str]
853-
) -> Optional[str]:
845+
key: str,
846+
distinct_id: ID_TYPES,
847+
match_value: Optional[FlagValue] = None,
848+
groups: Optional[Mapping[str, Union[str, int]]] = None,
849+
person_properties: Optional[Dict[str, Any]] = None,
850+
group_properties: Optional[Dict[str, Dict[str, Any]]] = None,
851+
only_evaluate_locally: bool = False,
852+
send_feature_flag_events: bool = True,
853+
disable_geoip: Optional[bool] = None,
854+
device_id: Optional[str] = None,
855+
) -> Optional[object]:
854856
"""
855857
Get the payload associated with a feature flag value.
856858
@@ -869,6 +871,11 @@ def get_feature_flag_payload(
869871
disable_geoip: Whether to disable GeoIP lookup.
870872
device_id: Optional device ID override for experience-continuity flags.
871873
874+
Returns:
875+
The payload associated with the matched feature flag value, or None.
876+
This function returns the payload only, not the FeatureFlagResult wrapper
877+
used internally to compute it.
878+
872879
Category:
873880
Feature flags
874881
"""
@@ -888,7 +895,7 @@ def get_feature_flag_payload(
888895

889896

890897
def get_remote_config_payload(
891-
key, # type: str
898+
key: str,
892899
):
893900
"""Get the payload for a remote config feature flag.
894901
@@ -908,14 +915,14 @@ def get_remote_config_payload(
908915

909916

910917
def get_all_flags_and_payloads(
911-
distinct_id,
912-
groups=None, # type: Optional[dict]
913-
person_properties=None, # type: Optional[dict]
914-
group_properties=None, # type: Optional[dict]
915-
only_evaluate_locally=False,
916-
disable_geoip=None, # type: Optional[bool]
917-
device_id=None, # type: Optional[str]
918-
flag_keys_to_evaluate=None, # type: Optional[list[str]]
918+
distinct_id: ID_TYPES,
919+
groups: Optional[Mapping[str, Union[str, int]]] = None,
920+
person_properties: Optional[Dict[str, Any]] = None,
921+
group_properties: Optional[Dict[str, Dict[str, Any]]] = None,
922+
only_evaluate_locally: bool = False,
923+
disable_geoip: Optional[bool] = None,
924+
device_id: Optional[str] = None,
925+
flag_keys_to_evaluate: Optional[list[str]] = None,
919926
) -> FlagsAndPayloads:
920927
"""
921928
Get all feature flag values and payloads for a user.
@@ -951,14 +958,14 @@ def get_all_flags_and_payloads(
951958

952959

953960
def evaluate_flags(
954-
distinct_id=None, # type: Optional[str]
955-
groups=None, # type: Optional[Dict[str, str]]
956-
person_properties=None, # type: Optional[Dict[str, Any]]
957-
group_properties=None, # type: Optional[Dict[str, Dict[str, Any]]]
958-
only_evaluate_locally=False, # type: bool
959-
disable_geoip=None, # type: Optional[bool]
960-
flag_keys=None, # type: Optional[list]
961-
device_id=None, # type: Optional[str]
961+
distinct_id: Optional[ID_TYPES] = None,
962+
groups: Optional[Mapping[str, Union[str, int]]] = None,
963+
person_properties: Optional[Dict[str, Any]] = None,
964+
group_properties: Optional[Dict[str, Dict[str, Any]]] = None,
965+
only_evaluate_locally: bool = False,
966+
disable_geoip: Optional[bool] = None,
967+
flag_keys: Optional[list[str]] = None,
968+
device_id: Optional[str] = None,
962969
) -> FeatureFlagEvaluations:
963970
"""Evaluate all feature flags for a user in a single call and return a
964971
:class:`FeatureFlagEvaluations` snapshot. Branch on ``.is_enabled()`` /

0 commit comments

Comments
 (0)