From d811ca2bf1e361a441418c6c8235513d7e213b26 Mon Sep 17 00:00:00 2001 From: "Vincent (Wen Yu) Ge" Date: Wed, 4 Nov 2026 14:38:45 -0500 Subject: [PATCH 1/8] docs: add the Markdown reference adapter Co-Authored-By: Claude Opus 5.5 --- bin/docs_scripts/check_reference.py | 195 ++++++++++ bin/docs_scripts/generate_markdown.py | 522 ++++++++++++++++++++++++++ bin/docs_scripts/render.mjs | 200 ++++++++++ bin/docs_scripts/render.test.mjs | 167 ++++++++ bin/docs_scripts/sdk_specs.py | 43 +++ docs/getting-started.md | 37 ++ posthog/__init__.py | 65 ++-- posthog/async_client.py | 27 +- posthog/capture_compression.py | 6 + posthog/client.py | 70 ++-- posthog/emails.py | 27 +- posthog/feature_flags.py | 6 + posthog/types.py | 14 +- 13 files changed, 1292 insertions(+), 87 deletions(-) create mode 100644 bin/docs_scripts/check_reference.py create mode 100644 bin/docs_scripts/generate_markdown.py create mode 100644 bin/docs_scripts/render.mjs create mode 100644 bin/docs_scripts/render.test.mjs create mode 100644 bin/docs_scripts/sdk_specs.py create mode 100644 docs/getting-started.md diff --git a/bin/docs_scripts/check_reference.py b/bin/docs_scripts/check_reference.py new file mode 100644 index 000000000..e96540e02 --- /dev/null +++ b/bin/docs_scripts/check_reference.py @@ -0,0 +1,195 @@ +"""End condition for the Python reference page. + +Builds its own inventory of the public Python surface with `ast`, independent of the extractor, +then checks the generated page against it and against the release rules: + + python bin/docs_scripts/check_reference.py + +Exits 1 and lists every failure. +""" + +import ast +import re +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent)) +import sdk_specs # noqa: E402 + +ROOT = Path(__file__).resolve().parents[2] + +STRUCTURE = {"Getting started", "SDK utils", "Configs", "Types"} +BANNED = { + "Used by section": r"^(#+ |\*\*)?Used by\b[:*]", + "preview wording": r"\b(preview|candidate)\b", + "commit SHA": r"\b[0-9a-f]{12,40}\b", + "empty class stub": r"^class \w+\(\):\s*$", + "scraped icon text": r"svg/svg", + "Sphinx role": r":(meth|func|class|attr|obj|data|exc|mod):`", + "Sphinx literal block": r"::$", + "variadic default": r"\*\*\w+[^,)\n]* = \{\}", + "private parameter": r"^- `_\w+`", + "old example label": r"\*\*(Good|Bad) example", + "unlabeled example": r"\*\*Example\.\*\*", + "list instead of table": r"^- `\w+` \(", +} + + +def public_defs(path: Path, class_name: str | None) -> list[str]: + tree = ast.parse(path.read_text()) + body = tree.body + if class_name: + body = next( + n for n in tree.body if isinstance(n, ast.ClassDef) and n.name == class_name + ).body + names = [ + n.name for n in body if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef)) + ] + return sorted( + {n for n in names if not n.startswith("_")} | ({"__init__"} & set(names)) + ) + + +def slugify(text: str) -> str: + return re.sub(r"[^\w\- ]", "", text.lower()).replace(" ", "-") + + +def main(md_path: str, specs: str) -> int: + root = ROOT + md = Path(md_path).read_text() + caps = sdk_specs.capabilities(specs) + failures = [] + headings = set(re.findall(r"^#{3,4} `([^`]+)`$", md, re.M)) + + inventory = { + "Client": public_defs(root / "posthog/client.py", "Client"), + "AsyncClient": public_defs(root / "posthog/async_client.py", "AsyncClient"), + "posthog": public_defs(root / "posthog/__init__.py", None), + } + for owner, names in inventory.items(): + for name in names: + label = ( + f"{owner}({owner == 'posthog' and '' or '...'})" + if name == "__init__" + else f"{owner}.{name}" + ) + if name == "__init__": + label = f"{owner}(...)" + if label not in headings: + failures.append(f"missing public symbol {label}") + + status = re.search(r"^status: (current|experimental)$", md, re.M) + if not status: + failures.append("front matter has no current or experimental status") + h2 = re.findall(r"^## (.+)$", md, re.M) + allowed = ( + STRUCTURE + | {c["title"] for c in caps["capabilities"].values()} + | {"AsyncClient", "Module functions"} + ) + failures += [f"unexpected top-level heading {h}" for h in h2 if h not in allowed] + + blocks = len( + re.findall( + r"^(Parameters|Fields|Settings) \(\d+\)$", md, re.M + ) + ) + if blocks != md.count("
"): + failures.append( + f"{blocks} member blocks but {md.count('
')} details blocks" + ) + tables = len( + re.findall( + r"^(Parameters|Fields|Settings) \(\d+\)\n\n\| (Parameter|Field|Setting) \|\n\| --- \|$", + md, + re.M, + ) + ) + if tables != blocks: + failures.append( + f"{blocks} member blocks but only {tables} open with a one-column table" + ) + if re.search(r"
", md) or re.search( + r"^#{3,6} `\w+` (parameter|field|setting)$", md, re.M + ): + failures.append("a bare
or a member heading is left on the page") + # Replay the site's heading slugger: every capability heading must keep its plain sdk-specs anchor. + seen: dict[str, int] = {} + ids: list[tuple[str, str, str]] = [] + in_fence = False + for line in md.split("\n"): + if line.lstrip().startswith("```"): + in_fence = not in_fence + heading = None if in_fence else re.match(r"^(#{1,6}) (.+)$", line) + if heading: + base = slugify(heading.group(2)) + n = seen.get(base, 0) + seen[base] = n + 1 + ids.append( + (heading.group(1), heading.group(2), base if n == 0 else f"{base}-{n}") + ) + for level, text, anchor in ids: + cap = caps["capabilities"].get(slugify(text)) + if level == "##" and cap and cap["title"] == text and anchor != slugify(text): + failures.append(f"capability {text!r} lost its anchor, got #{anchor}") + failures += [ + f"escaped pipe inside code span: {s!r}" + for s in re.findall(r"`([^`\n]+)`", md) + if "\\|" in s + ] + for block in re.findall( + r"Fields \(\d+\).*?
", md, re.S + ): + failures += [ + f"field default is a name, not a value: {d!r}" + for d in re.findall(r"\*\*Default:\*\* `([^`]+)`", block) + if re.fullmatch(r"[a-z_][\w.]*( or .+)?", d) + and d not in ("None", "True", "False") + ] + for entry in re.split(r"^(?=#{3,4} `)", md, flags=re.M): + if ( + entry.startswith(("### `AsyncClient", "#### `AsyncClient")) + and "**Available in:**" not in entry.split("\n#", 1)[0] + ): + failures.append( + f"{entry.splitlines()[0]} doesn't say it needs the async extra" + ) + types = md.split("## Types", 1)[-1] + for name in ( + "CaptureCompression", + "FeatureFlagEvaluationRuntime", + "UnresolvedFlagReason", + ): + section = types.split(f"### `{name}`", 1)[-1].split("\n### ", 1)[0] + if f"### `{name}`" in types and "**Value:**" not in section: + failures.append(f"enum {name} doesn't show its member values") + failures += [ + f"type method {m} has no description" + for m in re.findall( + r"^#### `([^`]+)`\n\n(?:```|
|\*\*Returns|\[Source)", types, re.M + ) + ] + for block in re.findall( + r"Fields \(\d+\)(.*?)
", types, re.S + ): + failures += [ + f"type field {r.split('`')[1]} has no description" + for r in re.findall(r"^\| (`.+) \|$", block, re.M) + if not re.search(r"
(?!\*\*)[^<]", r) + ] + prose = re.sub(r"```.*?```", "", md, flags=re.S) + for name, pattern in BANNED.items(): + for match in re.finditer(pattern, prose, re.M | re.I): + failures.append(f"{name}: {match.group(0)!r}") + + for failure in failures: + print(f"FAIL {failure}") + total = sum(len(v) for v in inventory.values()) + print( + f"{len(failures)} failures, {total} public symbols checked, {len(h2)} top-level headings" + ) + return 1 if failures else 0 + + +if __name__ == "__main__": + sys.exit(main(*sys.argv[1:])) diff --git a/bin/docs_scripts/generate_markdown.py b/bin/docs_scripts/generate_markdown.py new file mode 100644 index 000000000..9407e5324 --- /dev/null +++ b/bin/docs_scripts/generate_markdown.py @@ -0,0 +1,522 @@ +"""Generate this SDK's reference Markdown from source. + +Griffe reads the package statically, so the SDK is never imported. Docstrings are parsed as +Google style. A `posthog-capability: ` line maps a symbol to its sdk-specs capability, +and unmapped symbols land in SDK utils under their `Category:`. The shared renderer turns the +symbol list into Markdown, so every SDK's page has the same shape. + + python bin/docs_scripts/generate_markdown.py --sdk-specs [--major 8 --source-ref v1] +""" + +import argparse +import json +import logging +import re +import subprocess +import sys +import tempfile +import textwrap +from pathlib import Path +from typing import Any + +import griffe + +sys.path.insert(0, str(Path(__file__).resolve().parent)) +import sdk_specs # noqa: E402 + +ROOT = Path(__file__).resolve().parents[2] +SOURCE = "https://github.com/PostHog/posthog-python/blob/v{version}/{path}#L{line}" +REFERENCES = [ + ("Client", "client.Client", "Client"), + ("AsyncClient", "async_client.AsyncClient", "AsyncClient"), +] +TYPES = """args.OptionalCaptureArgs args.OptionalSetArgs feature_flag_evaluations.FeatureFlagEvaluations +types.FeatureFlagResult types.FeatureFlag types.FlagsAndPayloads types.FeatureFlagEvaluationRuntime +types.UnresolvedFlagReason flag_definition_cache.FlagDefinitionCacheProvider flag_definition_cache.FlagDefinitionCacheData +feature_flags.InconclusiveMatchError feature_flags.RequiresServerEvaluation capture_send.CaptureError +capture_compression.CaptureCompression tracing.span.Span +metrics_capture.PostHogMetrics emails.Emails emails.AsyncEmails emails.EmailError emails.EmailStatus +emails.SendEmailResult ai.prompts.Prompts""".split() +UTILS = { + "Framework integrations": """integrations.django.PosthogContextMiddleware integrations.flask.PosthogFlaskIntegration + integrations.asgi.PosthogASGIMiddleware integrations.celery.PosthogCeleryIntegration + integrations.drf.create_exception_handler integrations.otel.PostHogContextSpanProcessor + integrations.otel.enrich_current_span""".split(), + "HTTP connection": "request.set_socket_options request.enable_keep_alive request.disable_connection_reuse".split(), +} +WRAPPERS = "openai anthropic gemini langchain openai_agents claude_agent_sdk".split() +CONFIGS = [("integrations.django.PosthogContextMiddleware", "Django settings")] +# Where a symbol works when that isn't everywhere Python runs, by module. Extras are checked against pyproject.toml. +AVAILABLE_IN = { + "async_client": "the `async` extra, `posthog[async]`", + "integrations.django": "Django apps", + "integrations.drf": "Django REST framework apps", + "integrations.flask": "Flask apps", + "integrations.celery": "Celery workers", + "integrations.asgi": "ASGI apps, such as FastAPI and Starlette", + "integrations.otel": "the `otel` extra, `posthog[otel]`", + "ai.langchain": "the `langchain` extra, `posthog[langchain]`", + "ai.openai": "apps with the `openai` package", + "ai.anthropic": "apps with the `anthropic` package", + "ai.gemini": "apps with the Google Gen AI package", + "ai.openai_agents": "apps with the `openai-agents` package", + "ai.claude_agent_sdk": "apps with the `claude-agent-sdk` package", +} +LITERAL_VALUE = re.compile( + r"""^(None|True|False|-?\d[\d_.e]*|(["']).*\2|\[\]|\{\}|\(\))$""" +) +TAG = re.compile(r"^[ \t]*posthog-capability:[ \t]*([a-z0-9-]+)[ \t]*$\n?", re.M) +FENCE = re.compile(r"```(\w*)\n(.*?)```", re.S) +LABEL = re.compile(r"#\s*(Correct example|Counter example \(incorrect\))\.?:?\s*(.*)") +ROLE = re.compile( + r":(?:py:)?(?:meth|func|class|attr|obj|data|exc|mod|const):`~?([^`]+)`" +) +LITERAL = re.compile(r"^([^\n]*?)::\n\n((?:(?:[ \t]+[^\n]*)?\n)+)", re.M) + + +def text(value) -> str: + return str(value) if value is not None else "" + + +def md(value: str) -> str: + """Converts the Sphinx markup in these docstrings to Markdown: roles, double backticks, literal blocks.""" + value = re.sub(r"``([^`]+)``", r"`\1`", ROLE.sub(r"`\1`", value or "")) + + def block(m: re.Match) -> str: + lead = m.group(1) + ":\n\n" if m.group(1).strip() else "" + return lead + "```python\n" + textwrap.dedent(m.group(2)).strip() + "\n```\n\n" + + return LITERAL.sub(block, value + "\n").strip() + + +def get(pkg, path: str): + try: + return pkg[path] + except KeyError: + return None + + +def available_in(path: str) -> str | None: + module = path.removeprefix("posthog.") + return next( + ( + where + for prefix, where in AVAILABLE_IN.items() + if module == prefix or module.startswith(prefix + ".") + ), + None, + ) + + +def check_extras() -> None: + extras = re.search( + r"^\[project\.optional-dependencies\]\n(.*?)(?=^\[)", + (ROOT / "pyproject.toml").read_text(), + re.M | re.S, + ) + named = set(re.findall(r"^(\w+) = ", extras.group(1), re.M)) if extras else set() + missing = { + x for w in AVAILABLE_IN.values() for x in re.findall(r"posthog\[(\w+)\]", w) + } - named + if missing: + raise SystemExit( + f"AVAILABLE_IN names extras pyproject.toml doesn't define: {sorted(missing)}" + ) + + +def source(obj, version: str) -> str: + return SOURCE.format( + version=version, path=obj.relative_package_filepath, line=obj.lineno + ) + + +def parse(obj) -> tuple[str | None, list]: + raw = obj.docstring.value if obj.docstring else "" + tag = TAG.search(raw) + body = TAG.sub("", raw).strip() + sections = griffe.Docstring(body, parent=obj).parse("google") if body else [] + return (tag.group(1) if tag else None), sections + + +def examples(value) -> list[dict]: + out = [] + for _, code in FENCE.findall("\n".join(t for _, t in value)): + label = why = None + head, _, rest = code.lstrip("\n").partition("\n") + match = LABEL.match(head.strip()) + if match: + label, why, code = ( + "correct" if match.group(1).startswith("Correct") else "incorrect", + match.group(2) or None, + rest, + ) + out.append({"code": code, "label": label, "why": why}) + return out + + +def typed_dict_fields(pkg, annotation: str) -> dict: + match = re.fullmatch(r"Unpack\[(\w+)\]", annotation) + cls = get(pkg, f"args.{match.group(1)}") if match else None + return {n: text(a.annotation) for n, a in cls.attributes.items()} if cls else {} + + +def params(pkg, fn, documented: dict) -> list[dict]: + out = [] + for p in fn.parameters: + if p.name in ("self", "cls") or p.name.startswith("_"): + continue + kind = p.kind.value + if kind == "variadic keyword": + fields = typed_dict_fields(pkg, text(p.annotation)) + for name in [n for n in documented if n in fields]: + out.append( + { + "name": name, + "type": fields[name], + "description": md(documented.pop(name)), + } + ) + if fields: + continue + star = {"variadic positional": "*", "variadic keyword": "**"}.get(kind, "") + required = p.default is None and not star + out.append( + { + "name": star + p.name, + "type": text(p.annotation), + "default": None if star else p.default and text(p.default), + "required": required, + "description": md(documented.pop(p.name, "")), + } + ) + out += [{"name": n, "description": md(d)} for n, d in documented.items()] + return out + + +def signature(fn, name: str) -> str: + parts = [] + for p in fn.parameters: + if p.name in ("self", "cls") or p.name.startswith("_"): + continue + star = {"variadic positional": "*", "variadic keyword": "**"}.get( + p.kind.value, "" + ) + part = star + p.name + (f": {p.annotation}" if p.annotation is not None else "") + parts.append( + part + (f" = {p.default}" if p.default is not None and not star else "") + ) + ret = f" -> {fn.returns}" if fn.returns is not None and name[0].islower() else "" + prefix = "async " if "async" in fn.labels else "" + line = f"{prefix}{name}({', '.join(parts)}){ret}" + return ( + line + if len(line) <= 88 + else f"{prefix}{name}(\n" + "".join(f" {p},\n" for p in parts) + f"){ret}" + ) + + +def entry(pkg, obj, qualname: str, version: str, group: str | None = None) -> dict: + slug, sections = parse(obj) + e: dict[str, Any] = { + "qualname": qualname, + "capability": slug, + "group": group, + "description": "", + "examples": [], + "source": source(obj, version), + "availableIn": available_in(obj.module.path), + } + texts, notes, documented = [], [], {} + for s in sections: + kind = s.kind.value + if kind == "text": + texts.append(s.value) + elif kind in ("parameters", "other parameters"): + documented.update({p.name: p.description for p in s.value}) + elif kind == "returns": + e["returns"] = { + "type": text(s.value[0].annotation) + or text(getattr(obj, "returns", "")), + "description": md(" ".join(r.description for r in s.value)), + } + elif kind == "raises": + e["raises"] = [ + {"type": text(r.annotation), "description": md(r.description)} + for r in s.value + ] + elif kind == "examples": + e["examples"] += examples(s.value) + elif kind == "deprecated": + e["deprecated"] = s.value.description + elif kind == "admonition" and s.title in ("Example", "Examples"): + body = s.value.description + e["examples"] += ( + examples([(None, body)]) + if "```" in body + else [{"code": body, "label": None, "why": None}] + ) + elif kind == "admonition" and s.title == "Category": + e["group"] = group or s.value.description.strip() + elif kind == "admonition": + notes.append(f"**{s.title}.** {md(s.value.description)}") + e["description"], e["notes"] = md("\n\n".join(texts)), "\n\n".join(notes) + if obj.is_function: + name = qualname.removesuffix("(...)") + e["signature"] = signature(obj, name if obj.name == "__init__" else obj.name) + e["params"] = params(pkg, obj, documented) + if not e.get("returns") and obj.returns is not None and obj.name != "__init__": + e["returns"] = {"type": text(obj.returns)} + elif obj.is_class and "__init__" in obj.members: + init = obj.members["__init__"] + e["signature"] = signature(init, obj.name) + e["params"] = params( + pkg, + init, + documented + | { + p.name: p.description + for s in parse(init)[1] + if s.kind.value == "parameters" + for p in s.value + }, + ) + elif obj.is_attribute: + e["signature"] = ( + f"{qualname}: {obj.annotation}" if obj.annotation is not None else None + ) + return e + + +def members(cls) -> list: + return [ + m + for n, m in cls.members.items() + if not m.is_alias + and (not n.startswith("_") or n == "__init__") + and (m.is_function or "property" in m.labels) + ] + + +def documented_fields(cls) -> dict: + """Field descriptions from the class's `Attributes:`, `Args:` and `Values:` sections, and from `__init__`'s `Args:`.""" + sections = parse(cls)[1] + ( + parse(cls.members["__init__"])[1] if "__init__" in cls.members else [] + ) + found = { + item.name: item.description + for s in sections + if s.kind.value in ("attributes", "parameters") + for item in s.value + } + for s in sections: + if s.kind.value == "admonition" and s.title == "Values": + found |= { + n: " ".join(d.split()) + for n, d in re.findall( + r"^(\w+): (.*?)(?=^\w+:|\Z)", s.value.description, re.M | re.S + ) + } + return found + + +def is_data(cls) -> bool: + """Dataclasses, TypedDicts, enums and errors carry fields readers use. Service objects carry only state.""" + bases = " ".join(text(b) for b in cls.bases) + return any("dataclass" in text(d.value) for d in cls.decorators) or any( + k in bases for k in ("TypedDict", "Enum", "Exception", "Error") + ) + + +def type_info(pkg, cls, version: str) -> dict: + _, sections = parse(cls) + documented = documented_fields(cls) + # Only a literal is a default. A field set from an __init__ argument shows that argument's name instead. + enum = any("Enum" in text(b) for b in cls.bases) + + def value(a): + return text(a.value) if LITERAL_VALUE.match(text(a.value)) else None + + fields = [ + { + "name": n, + "type": None if enum else text(a.annotation), + "default": None if enum else value(a), + "value": value(a) if enum else None, + "description": md( + a.docstring.value if a.docstring else documented.get(n, "") + ), + } + for n, a in cls.attributes.items() + if is_data(cls) and not n.startswith("_") and "property" not in a.labels + ] + methods = [ + entry(pkg, m, f"{cls.name}.{m.name}", version) + for m in members(cls) + if m.name != "__init__" + ] + description = md("\n\n".join(s.value for s in sections if s.kind.value == "text")) + return { + "name": cls.name, + "description": description, + "fields": fields, + "methods": methods, + "source": source(cls, version), + } + + +def configs(pkg) -> list[dict]: + out = [] + for path, where in CONFIGS: + cls = get(pkg, path) + doc = cls.docstring.value if cls and cls.docstring else "" + for name, desc in re.findall( + r"^\s*- `(POSTHOG_[A-Z_]+)`: (.*?)(?=^\s*- `|^\s*$)", doc, re.M | re.S + ): + out.append( + { + "name": name, + "where": where, + "description": md(" ".join(desc.split())), + } + ) + return out + + +def page(pkg, version: str, major: int, status: str) -> dict: + refs: list[dict[str, Any]] = [] + for title, path, prefix in REFERENCES: + cls = pkg[path] + refs.append( + { + "title": title, + "entries": [ + entry( + pkg, + m, + f"{prefix}(...)" + if m.name == "__init__" + else f"{prefix}.{m.name}", + version, + ) + for m in members(cls) + ], + } + ) + module = [ + entry(pkg, m, f"posthog.{n}", version) + for n, m in pkg.members.items() + if not m.is_alias and m.is_function and not n.startswith("_") + ] + refs.append({"title": "Module functions", "entries": module}) + for group, paths in UTILS.items(): + for path in paths: + if (obj := get(pkg, path)) is not None: + refs[0]["entries"].append( + entry(pkg, obj, f"posthog.{path}", version, group) + ) + if obj.is_class: + refs[0]["entries"] += [ + entry(pkg, m, f"{obj.name}.{m.name}", version, group) + for m in members(obj) + if m.name != "__init__" + ] + for name in WRAPPERS: + if (mod := get(pkg, f"ai.{name}")) is not None: + names = ", ".join( + f"`{n}`" + for n in mod.members + if not n.startswith("_") and n[0].isupper() + ) + summary = md(mod.docstring.value.split("\n\n")[0]) if mod.docstring else "" + refs[0]["entries"].append( + { + "qualname": f"posthog.ai.{name}", + "group": "AI wrapper clients", + "availableIn": available_in(f"ai.{name}"), + "description": " ".join( + filter(None, [summary, f"Exports {names}."]) + ), + } + ) + # Newest guide first, so the migration page opens on the latest upgrade. + migrations = sorted((ROOT / "docs").glob("migration-*.md"), reverse=True) + return { + "sdk": "python", + "title": "PostHog Python SDK", + "version": version, + "major": major, + "status": status, + "language": "python", + "gettingStarted": (ROOT / "docs/getting-started.md").read_text(), + "references": refs, + "configs": configs(pkg), + "types": [type_info(pkg, c, version) for p in TYPES if (c := get(pkg, p))], + "migrations": [m.read_text() for m in migrations], + } + + +def main() -> None: + args = argparse.ArgumentParser() + args.add_argument( + "--sdk-specs", + required=True, + help="Checkout of PostHog/sdk-specs to take capabilities from", + ) + args.add_argument( + "--renderer", default=str(Path(__file__).resolve().parent / "render.mjs") + ) + args.add_argument( + "--major", + type=int, + help="Major this branch targets, defaults to the declared version's major", + ) + args.add_argument( + "--source-ref", + help="Git ref for source links, defaults to the release tag v", + ) + opts = args.parse_args() + logging.getLogger("griffe").setLevel(logging.ERROR) + check_extras() + pkg = griffe.load( + "posthog", + search_paths=[str(ROOT)], + docstring_parser="google", + resolve_aliases=False, + ) + found = re.search(r'VERSION = "([^"]+)"', (ROOT / "posthog/version.py").read_text()) + if found is None: + raise SystemExit("posthog/version.py has no VERSION") + declared = found.group(1) + major = opts.major or int(declared.split(".")[0]) + # A branch that targets a newer major than it has released documents an experimental reference. + status = "current" if int(declared.split(".")[0]) >= major else "experimental" + version = declared if status == "current" else "unreleased" + global SOURCE + SOURCE = SOURCE.replace("v{version}", opts.source_ref or "v{version}") + data = page(pkg, version, major, status) + outputs = { + f"posthog-python-{data['major']}.md": [], + "posthog-python-migration.md": ["--migration"], + } + with ( + tempfile.NamedTemporaryFile("w", suffix=".json") as f, + tempfile.NamedTemporaryFile("w", suffix=".json") as caps, + ): + json.dump(data, f) + json.dump(sdk_specs.capabilities(opts.sdk_specs), caps) + f.flush() + caps.flush() + for name, flag in outputs.items(): + md = subprocess.run( + ["node", opts.renderer, f.name, caps.name, *flag], + check=True, + capture_output=True, + text=True, + ).stdout + if md: + (ROOT / "references" / name).write_text(md) + print(f"wrote references/{name}") + + +if __name__ == "__main__": + main() diff --git a/bin/docs_scripts/render.mjs b/bin/docs_scripts/render.mjs new file mode 100644 index 000000000..aee720468 --- /dev/null +++ b/bin/docs_scripts/render.mjs @@ -0,0 +1,200 @@ +#!/usr/bin/env node +// Renders one SDK's symbol list into its reference page Markdown. +// +// Every SDK's extractor hands this the same JSON shape, so every page comes out the same way. +// Capabilities come from sdk-specs: an entry's `capability` slug picks its area and heading. +// Entries with no capability go to SDK utils. Usage: +// +// node render.mjs > reference.md +import { readFileSync } from 'node:fs' +import { fileURLToPath } from 'node:url' + +/** The heading id the docs site and GitHub give a heading with this text. */ +export function slugify(text) { + return text + .toLowerCase() + .replace(/[^\p{L}\p{N}\- ]/gu, '') + .replace(/ /g, '-') +} + +const fence = (code, lang) => '```' + lang + '\n' + code.trim() + '\n```' +const code = (text) => (text ? '`' + String(text).replace(/\s*\n\s*/g, ' ').trim() + '`' : '') +/** + * Table cell text. Code fences become inline code, paragraphs join with
, and pipes are + * escaped. A pipe inside a code span splits it into spans around `\|`, since the site's MDX shows + * `\|` inside one span literally. + */ +const cellText = (text) => + String(text ?? '') + .trim() + .replace(/```\w*\n([\s\S]*?)```/g, (_, c) => '`' + c.trim().replace(/\s*\n\s*/g, '; ') + '`') + .split(/\n\s*\n/) + .map((paragraph) => paragraph.replace(/\s*\n\s*/g, ' ')) + .join('
') + .split(/(`[^`]*`)/) + .map((part) => + !part.startsWith('`') + ? part.replace(/\|/g, '\\|') + : part.slice(1, -1).split(/\s*\|\s*/).map((x) => '`' + x + '`').join(' \\| ') + ) + .join('') + +/** + * One details block holding a one-column table: a row per parameter, field or setting with its + * name, a labeled type and default line, then its description. + */ +function members(title, rows) { + if (!rows?.length) return null + const head = { Parameters: 'Parameter', Fields: 'Field', Settings: 'Setting' }[title] + const lines = rows.map((r) => { + const labels = [ + r.where && `**Where:** ${r.where}`, + r.value && `**Value:** ${cellText(code(r.value))}`, + r.type && `**Type:** ${cellText(code(r.type))}`, + r.required ? '**Required**' : r.default && `**Default:** ${cellText(code(r.default))}`, + ].filter(Boolean) + const cell = [code(r.name), labels.join(' · '), r.description && cellText(r.description)].filter(Boolean).join('
') + return `| ${cell} |` + }) + return ['
', `${title} (${rows.length})`, '', `| ${head} |`, '| --- |', ...lines, '', '
'].join('\n') +} + +const sentence = (text) => (text ? text[0].toUpperCase() + text.slice(1) + (/[.!?]$/.test(text) ? '' : '.') : '') + +function example(ex, lang) { + const label = + // Source examples are correct usage unless marked as a counter example. + ex.label === 'incorrect' ? `**Counter example (incorrect).** ${sentence(ex.why)}`.trim() : '**Correct example.**' + return `${label}\n\n${fence(ex.code, lang)}` +} + +function entry(e, level, lang) { + const out = [`${'#'.repeat(level)} \`${e.qualname}\``] + if (e.deprecated) out.push(`**Deprecated.** ${e.deprecated}`) + if (e.availableIn) out.push(`**Available in:** ${e.availableIn}`) + if (e.description) out.push(e.description) + if (e.signature) out.push(fence(e.signature, lang)) + const params = members('Parameters', e.params) + if (params) out.push(params) + if (e.returns && (e.returns.type || e.returns.description)) { + out.push(`**Returns** ${code(e.returns.type)}${e.returns.description ? `. ${e.returns.description}` : ''}`.trim()) + } + for (const r of e.raises ?? []) out.push(`**Raises** ${code(r.type)}. ${r.description ?? ''}`.trim()) + if (e.notes) out.push(e.notes) + for (const ex of e.examples ?? []) out.push(example(ex, lang)) + if (e.source) out.push(`[Source](${e.source})`) + return out.join('\n\n') +} + +/** Moves every heading down one level, dropping the H1 when asked. Code fences stay as written. */ +function nest(markdown, { dropTitle = true } = {}) { + let inFence = false + return markdown + .trim() + .split('\n') + .flatMap((line) => { + if (/^\s*(```|~~~)/.test(line)) inFence = !inFence + if (inFence || !/^#{1,5} /.test(line)) return [line] + return dropTitle && line.startsWith('# ') ? [] : ['#' + line] + }) + .join('\n') + .replace(/^\n+/, '') +} + +function capabilityOf(e, index) { + const cap = index.capabilities[e.capability] + if (!cap) throw new Error(`Unknown capability "${e.capability}" on ${e.qualname}`) + if (slugify(cap.title) !== e.capability) throw new Error(`Title "${cap.title}" doesn't produce slug "${e.capability}"`) + return cap +} + +const order = (index) => (e) => { + const cap = index.capabilities[e.capability] + return [index.areas.indexOf(cap.area), Object.keys(index.capabilities).indexOf(e.capability)] +} + +function byOrder(entries, index) { + const key = order(index) + return [...entries].sort((a, b) => { + const [x, y] = [key(a), key(b)] + return x[0] - y[0] || x[1] - y[1] + }) +} + +function groups(entries, keyOf) { + const map = new Map() + for (const e of entries) map.set(keyOf(e), [...(map.get(keyOf(e)) ?? []), e]) + return map +} + +function defaultReference(ref, index, lang) { + const mapped = ref.entries.filter((e) => e.capability) + mapped.forEach((e) => capabilityOf(e, index)) + const out = [] + // Each sdk-specs capability is a top-level section, in area order. Its heading's anchor is the shared slug. + for (const [slug, entries] of groups(byOrder(mapped, index), (e) => e.capability)) { + out.push(`## ${index.capabilities[slug].title}`, ...entries.map((e) => entry(e, 3, lang))) + } + const utils = ref.entries.filter((e) => !e.capability) + if (utils.length) { + out.push('## SDK utils') + for (const [group, entries] of groups(utils, (e) => e.group ?? 'Other')) { + out.push(`### ${group}`, ...entries.map((e) => entry(e, 4, lang))) + } + } + return out +} + +function secondaryReference(ref, index, lang) { + const mapped = ref.entries.filter((e) => e.capability) + mapped.forEach((e) => capabilityOf(e, index)) + const entries = [...byOrder(mapped, index), ...ref.entries.filter((e) => !e.capability)] + return [`## ${ref.title}`, ...(ref.intro ? [ref.intro] : []), ...entries.map((e) => entry(e, 3, lang))] +} + +function type(t, lang) { + if (!t.description && !t.fields?.length && !t.methods?.length) return [] + const out = [`### \`${t.name}\``] + if (t.description) out.push(t.description) + const fields = members('Fields', t.fields) + if (fields) out.push(fields) + out.push(...(t.methods ?? []).map((m) => entry(m, 4, lang))) + if (t.source) out.push(`[Source](${t.source})`) + return [out.join('\n\n')] +} + +export function render(page, index) { + const lang = page.language + const [first, ...rest] = page.references + // current: the released major customers install. experimental: a newer major that hasn't shipped. + if (!['current', 'experimental'].includes(page.status)) throw new Error(`Unknown status "${page.status}"`) + // Getting started renders before the reference, so its headings would take capability anchors first. + for (const [, title] of page.gettingStarted.matchAll(/^#{2,6} (.+)$/gm)) { + if (index.capabilities[slugify(title)]) throw new Error(`Getting started heading "${title}" takes the "${slugify(title)}" anchor`) + } + const sections = [ + ['---', `title: ${page.title}`, `sdk: ${page.sdk}`, `version: ${page.version}`, `major: ${page.major}`, `status: ${page.status}`, '---'].join('\n'), + '## Getting started', + nest(page.gettingStarted), + ...defaultReference(first, index, lang), + ...rest.flatMap((ref) => secondaryReference(ref, index, lang)), + ] + const configs = members('Settings', page.configs) + if (configs) sections.push('## Configs', configs) + const types = (page.types ?? []).flatMap((t) => type(t, lang)) + if (types.length) sections.push('## Types', ...types) + return sections.join('\n\n') + '\n' +} + +/** The SDK's migration guides as their own page, newest first. Each guide's title becomes an H2. */ +export function renderMigration(page) { + if (!page.migrations?.length) return null + const head = ['---', `title: ${page.title} migration guides`, `sdk: ${page.sdk}`, 'kind: migration', '---'].join('\n') + return [head, ...page.migrations.map((guide) => nest(guide, { dropTitle: false }))].join('\n\n') + '\n' +} + +if (process.argv[1] === fileURLToPath(import.meta.url)) { + const [pagePath, indexPath, flag] = process.argv.slice(2) + const read = (p) => JSON.parse(readFileSync(p, 'utf8')) + process.stdout.write((flag === '--migration' ? renderMigration(read(pagePath)) : render(read(pagePath), read(indexPath))) ?? '') +} diff --git a/bin/docs_scripts/render.test.mjs b/bin/docs_scripts/render.test.mjs new file mode 100644 index 000000000..ad09ae3de --- /dev/null +++ b/bin/docs_scripts/render.test.mjs @@ -0,0 +1,167 @@ +import { test } from 'node:test' +import assert from 'node:assert/strict' +import { render, renderMigration, slugify } from './render.mjs' + +const capabilities = { + areas: ['Events', 'Feature flags', 'Lifecycle'], + capabilities: { + capture: { title: 'Capture', area: 'Events' }, + 'get-feature-flag': { title: 'Get feature flag', area: 'Feature flags' }, + flush: { title: 'Flush', area: 'Lifecycle' }, + }, +} + +const capture = { + qualname: 'Client.capture', + capability: 'capture', + signature: 'capture(event: str, **kwargs) -> Optional[str]', + description: 'Captures an event.', + params: [ + { name: 'event', type: 'str', required: true, description: 'The event name.' }, + { name: 'timestamp', type: 'datetime | str', default: 'None', description: 'When it happened | UTC.' }, + { name: 'secret_key', default: 'None', description: 'A secret key.\nExample:\n\n```python\nClient(key, secret_key="phx_")\n```' }, + ], + returns: { type: 'Optional[str]', description: 'The event UUID, or None when it was dropped.' }, + examples: [ + { code: 'client.capture("signed_up", distinct_id="u1")' }, + { label: 'incorrect', why: 'fails because `distinct_id` is keyword-only', code: 'client.capture("u1", "signed_up")' }, + { label: 'correct', code: 'client.capture("signed_up", distinct_id="u1")' }, + ], +} + +const page = { + sdk: 'python', + title: 'PostHog Python SDK', + version: '8.0.0', + major: 8, + status: 'experimental', + language: 'python', + gettingStarted: '# Getting started\n\nInstall it.\n\n## Install\n\n```bash\npip install posthog\n```', + references: [ + { + title: 'Client', + entries: [ + { qualname: 'Client.flush', capability: 'flush', description: 'Sends queued events.' }, + capture, + { qualname: 'Client.tag', capability: null, group: 'Contexts', description: 'Adds a tag.' }, + ], + }, + { + title: 'AsyncClient', + entries: [{ qualname: 'AsyncClient.capture', capability: 'capture', description: 'Captures an event.' }], + }, + ], + configs: [{ name: 'POSTHOG_MW_CAPTURE_EXCEPTIONS', where: 'Django settings', type: 'bool', default: 'True', description: 'Captures exceptions.' }], + types: [ + { + name: 'FeatureFlagEvaluations', + kind: 'class', + description: 'A snapshot of flag values.', + fields: [], + methods: [{ qualname: 'FeatureFlagEvaluations.is_enabled', signature: 'is_enabled(key: str) -> bool', description: 'Checks a flag.' }], + }, + { name: 'Empty', kind: 'class', description: '', fields: [], methods: [] }, + { name: 'Compression', description: 'Request compression.', fields: [{ name: 'GZIP', value: '"gzip"', description: 'gzip, from the standard library.' }], methods: [] }, + ], + migrations: ['# Migrating from 7.x to 8.0\n\n## Capture\n\nIt changed.', '# Migrating from 6.x to 7.0\n\nOlder.'], +} + +const md = render(page, capabilities) +const h2 = md.split('\n').filter((l) => l.startsWith('## ')).map((l) => l.slice(3)) + +test('slugify matches heading ids', () => { + assert.equal(slugify('Get feature flag'), 'get-feature-flag') + assert.equal(slugify('Get anonymous ID'), 'get-anonymous-id') + assert.equal(slugify('`Client.capture`'), 'clientcapture') +}) + +test('top-level headings are capabilities in area order, then structure', () => { + assert.deepEqual(h2, ['Getting started', 'Capture', 'Flush', 'SDK utils', 'AsyncClient', 'Configs', 'Types']) +}) + +test('capability headings carry the shared slug', () => { + assert.match(md, /^## Capture$/m) + assert.equal(slugify('Capture'), 'capture') + assert.match(md, /^## Flush$/m) + assert.doesNotMatch(md, /^## Events$/m) +}) + +test('unknown capabilities and slug mismatches throw', () => { + const bad = { ...page, references: [{ title: 'Client', entries: [{ qualname: 'x', capability: 'nope' }] }] } + assert.throws(() => render(bad, capabilities), /Unknown capability "nope"/) + const mismatch = { ...capabilities, capabilities: { ...capabilities.capabilities, capture: { title: 'Capture events', area: 'Events' } } } + assert.throws(() => render(page, mismatch), /slug/) +}) + +test('a getting started heading that would take a capability slug throws', () => { + const clash = { ...page, gettingStarted: '# Getting started\n\n## Capture\n\nSend one.' } + assert.throws(() => render(clash, capabilities), /Getting started heading "Capture" takes the "capture" anchor/) +}) + +test('an entry renders signature, parameters, returns and labeled examples', () => { + assert.match(md, /^### `Client\.capture`$/m) + assert.match(md, /```python\ncapture\(event: str, \*\*kwargs\) -> Optional\[str\]\n```/) + assert.match(md, /
\nParameters \(3\)<\/summary>\n\n\| Parameter \|\n\| --- \|\n\| `event`
\*\*Type:\*\* `str` · \*\*Required\*\*
The event name\. \|\n/) + assert.match(md, /^\| `timestamp`
\*\*Type:\*\* `datetime` \\\| `str` · \*\*Default:\*\* `None`
When it happened \\\| UTC\. \|$/m) + assert.match(md, /^\| `secret_key`
\*\*Default:\*\* `None`
A secret key\. Example:
`Client\(key, secret_key="phx_"\)` \|\n\n<\/details>/m) + assert.doesNotMatch(md, /^#####/m) + assert.match(md, /\*\*Returns\*\* `Optional\[str\]`\. The event UUID/) + assert.match(md, /\*\*Correct example\.\*\*\n\n```python\nclient\.capture\("signed_up", distinct_id="u1"\)\n```\n\n\*\*Counter/) + assert.doesNotMatch(md, /\*\*Example\.\*\*/) + assert.match(md, /\*\*Counter example \(incorrect\)\.\*\* Fails because `distinct_id` is keyword-only\.\n\n```python/) + assert.match(md, /\*\*Correct example\.\*\*\n\n```python\nclient\.capture\("signed_up", distinct_id="u1"\)/) +}) + +test('member names sit in table rows, so they never take a capability anchor', () => { + const clash = { ...page, references: [{ title: 'Client', entries: [{ qualname: 'Client(...)', capability: 'capture', params: [{ name: 'flush', default: 'None' }] }, { qualname: 'Client.flush', capability: 'flush' }] }] } + const out = render(clash, capabilities) + assert.match(out, /^\| `flush`
\*\*Default:\*\* `None` \|$/m) + assert.doesNotMatch(out, /^#+ `flush`/m) +}) + +test('every parameter block sits in a details block, even a short one', () => { + const one = { ...page, references: [{ title: 'Client', entries: [{ qualname: 'Client.flush', capability: 'flush', params: [{ name: 'timeout', default: '5' }] }] }] } + assert.match(render(one, capabilities), /
\nParameters \(1\)<\/summary>\n\n\| Parameter \|\n\| --- \|\n\| `timeout`
\*\*Default:\*\* `5` \|\n\n<\/details>/) + assert.equal((md.match(/
/g) ?? []).length, (md.match(/<\/details>/g) ?? []).length) + assert.doesNotMatch(md, /^- `/m) +}) + +test('unmapped entries land in SDK utils under their group', () => { + const utils = md.slice(md.indexOf('## SDK utils')) + assert.match(utils, /^### Contexts\n\n#### `Client\.tag`/m) +}) + +test('secondary references list entries without area headings', () => { + const asyncRef = md.slice(md.indexOf('## AsyncClient'), md.indexOf('## Configs')) + assert.match(asyncRef, /^### `AsyncClient\.capture`$/m) + assert.doesNotMatch(asyncRef, /^### Capture$/m) +}) + +test('types explain members and skip empty stubs', () => { + assert.match(md, /^### `FeatureFlagEvaluations`\n\nA snapshot of flag values\./m) + assert.match(md, /^#### `FeatureFlagEvaluations\.is_enabled`$/m) + assert.doesNotMatch(md, /`Empty`/) + assert.match(md, /^\| `GZIP`
\*\*Value:\*\* `"gzip"`
gzip, from the standard library\. \|$/m) +}) + +test('guides drop their H1 and nest under their section', () => { + assert.match(md, /## Getting started\n\nInstall it\.\n\n### Install/) + assert.doesNotMatch(md, /^# /m) +}) + +test('configs render as headed settings and the page has no reverse usage lists', () => { + assert.match(md, /^## Configs\n\n
\nSettings \(1\)<\/summary>\n\n\| Setting \|\n\| --- \|\n\| `POSTHOG_MW_CAPTURE_EXCEPTIONS`
\*\*Where:\*\* Django settings · \*\*Type:\*\* `bool` · \*\*Default:\*\* `True`
Captures exceptions\. \|$/m) + assert.doesNotMatch(md, /Used by/i) +}) + +test('front matter names the SDK and version', () => { + assert.match(md, /^---\ntitle: PostHog Python SDK\nsdk: python\nversion: 8\.0\.0\nmajor: 8\nstatus: experimental\n---\n/) + assert.throws(() => render({ ...page, status: 'preview' }, capabilities), /status/) +}) + +test('migration guides render as their own page, each guide an H2', () => { + const out = renderMigration(page) + assert.match(out, /^---\ntitle: PostHog Python SDK migration guides\nsdk: python\nkind: migration\n---\n\n## Migrating from 7\.x to 8\.0\n\n### Capture\n\nIt changed\.\n\n## Migrating from 6\.x to 7\.0\n\nOlder\.\n$/) + assert.doesNotMatch(md, /Migrating from/) + assert.equal(renderMigration({ ...page, migrations: [] }), null) +}) diff --git a/bin/docs_scripts/sdk_specs.py b/bin/docs_scripts/sdk_specs.py new file mode 100644 index 000000000..6a4710636 --- /dev/null +++ b/bin/docs_scripts/sdk_specs.py @@ -0,0 +1,43 @@ +"""Read sdk-specs capabilities for the reference: each slug's title, in the README table's order. + +The slug is the folder name under `openspec/specs/`, and it becomes the capability's anchor on +the reference page. Pass a checkout of PostHog/sdk-specs at the commit to document against. +""" + +import re +import subprocess +from pathlib import Path + +ACRONYMS = {"id": "ID", "ai": "AI", "http": "HTTP", "mcp": "MCP"} + + +def sentence_case(name: str) -> str: + words = [ + ACRONYMS.get(w.lower(), w if w.isupper() and len(w) > 1 else w.lower()) + for w in name.split() + ] + words[0] = words[0][0].upper() + words[0][1:] + return " ".join(words) + + +def capabilities(checkout: str) -> dict: + """The renderer's capability index. It fails when a spec folder has no README row.""" + root = Path(checkout) + readme = (root / "README.md").read_text() + rows = re.findall( + r"^\| ([^|]+?) \| \[`openspec/specs/([a-z0-9-]+)/spec\.md`\]", readme, re.M + ) + if not rows: + raise SystemExit(f"{root / 'README.md'} has no capability table") + caps = {slug: {"title": sentence_case(name)} for name, slug in rows} + folders = {p.name for p in (root / "openspec/specs").iterdir() if p.is_dir()} + if folders - set(caps): + raise SystemExit( + f"sdk-specs capabilities missing from its README table: {sorted(folders - set(caps))}" + ) + head = subprocess.run( + ["git", "-C", str(root), "rev-parse", "--short", "HEAD"], + capture_output=True, + text=True, + ) + return {"sdkSpecs": head.stdout.strip(), "areas": [], "capabilities": caps} diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 000000000..15d31c292 --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,37 @@ +# Getting started + +The Python SDK captures events, evaluates feature flags and reports errors from Python 3.10 or later. + +## Install + +```bash +pip install posthog +``` + +## Initialize + +Create one client for your app with your project token and host. Your project token and host are in your [project settings](https://app.posthog.com/project/settings). Keep the token in an environment variable rather than in code. + +```python +from posthog import Posthog + +posthog = Posthog("", host="") +``` + +## Capture an event + +```python +posthog.capture("user_signed_up", distinct_id="user_123", properties={"plan": "free"}) +``` + +`capture` queues the event and returns right away. A background thread sends it. + +## Shut down + +Call `shutdown()` before your process exits, so queued events are sent. + +```python +posthog.shutdown() +``` + +In an asyncio app, use `AsyncPosthog` as an async context manager instead. See [AsyncClient](#asyncclient). diff --git a/posthog/__init__.py b/posthog/__init__.py index d9f6d174f..81ee63f27 100644 --- a/posthog/__init__.py +++ b/posthog/__init__.py @@ -530,8 +530,7 @@ def capture(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str]: } ) ``` - Category: - Events + posthog-capability: capture """ return _proxy("capture", event, **kwargs) @@ -566,8 +565,7 @@ def capture_ai(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[st ) ``` - Category: - Events + posthog-capability: capture-ai """ return _proxy("capture_ai", event, **kwargs) @@ -598,8 +596,7 @@ def set(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str]: from posthog import set set(distinct_id='distinct_id', properties={'name': 'Max Hedgehog'}) ``` - Category: - Identification + posthog-capability: identify """ return _proxy("set", **kwargs) @@ -632,8 +629,7 @@ def set_once(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str]: set_once(distinct_id='distinct_id', properties={'initial_url': '/blog'}) ``` - Category: - Identification + posthog-capability: identify """ return _proxy("set_once", **kwargs) @@ -671,8 +667,7 @@ def group_identify( 'employees': 11 }) ``` - Category: - Identification + posthog-capability: group-identify """ return _proxy( @@ -714,8 +709,7 @@ def alias( from posthog import alias alias(previous_id='distinct_id', distinct_id='alias_id') ``` - Category: - Identification + posthog-capability: alias """ return _proxy( @@ -754,8 +748,7 @@ def capture_exception( except Exception as e: capture_exception(e) ``` - Category: - Events + posthog-capability: capture-exception """ return _proxy("capture_exception", exception=exception, **kwargs) @@ -797,8 +790,7 @@ def feature_enabled( if is_my_flag_enabled: matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user') ``` - Category: - Feature flags + posthog-capability: is-feature-enabled """ return _proxy( "feature_enabled", @@ -850,8 +842,7 @@ def get_feature_flag( if enabled_variant == 'variant-key': matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user') ``` - Category: - Feature flags + posthog-capability: get-feature-flag """ return _proxy( "get_feature_flag", @@ -899,8 +890,7 @@ def get_all_flags( from posthog import get_all_flags get_all_flags('distinct_id_of_your_user') ``` - Category: - Feature flags + posthog-capability: get-feature-flags """ return _proxy( "get_all_flags", @@ -955,6 +945,8 @@ def get_feature_flag_result( print(f"Variant: {result.variant}") print(f"Payload: {result.payload}") ``` + + posthog-capability: get-feature-flag-result """ return _proxy( "get_feature_flag_result", @@ -1005,8 +997,7 @@ def get_feature_flag_payload( This function returns the payload only, not the FeatureFlagResult wrapper used internally to compute it. - Category: - Feature flags + posthog-capability: get-feature-flag-payload """ return _proxy( "get_feature_flag_payload", @@ -1070,8 +1061,7 @@ def get_all_flags_and_payloads( Returns: A dict with ``featureFlags`` and ``featureFlagPayloads`` entries. - Category: - Feature flags + posthog-capability: get-feature-flags-and-payloads """ return _proxy( "get_all_flags_and_payloads", @@ -1134,8 +1124,7 @@ def evaluate_flags( capture("page_viewed", distinct_id="user_123", flags=flags) ``` - Category: - Feature flags + posthog-capability: evaluate-flags """ return _proxy( "evaluate_flags", @@ -1163,8 +1152,7 @@ def feature_flag_definitions(): definitions = feature_flag_definitions() ``` - Category: - Feature flags + posthog-capability: flag-definition-loader """ return _proxy("feature_flag_definitions") @@ -1227,8 +1215,7 @@ def load_feature_flags(): load_feature_flags() ``` - Category: - Feature flags + posthog-capability: flag-definition-loader """ return _proxy("load_feature_flags") @@ -1270,8 +1257,7 @@ def flush(timeout_seconds: Optional[float] = 10) -> None: posthog.flush(timeout_seconds=10) ``` - Category: - Client management + posthog-capability: flush """ _proxy("flush", timeout_seconds=timeout_seconds) @@ -1290,8 +1276,7 @@ def join() -> None: join() ``` - Category: - Client management + posthog-capability: shutdown """ _proxy("join") @@ -1338,8 +1323,7 @@ def start_span( outgoing_headers = {"traceparent": span.traceparent()} ``` - Category: - Tracing + posthog-capability: traces """ return _proxy( "start_span", @@ -1367,8 +1351,7 @@ def get_active_span() -> Optional[Span]: headers["traceparent"] = span.traceparent() ``` - Category: - Tracing + posthog-capability: traces """ return _proxy("get_active_span") @@ -1403,8 +1386,7 @@ def shutdown() -> None: shutdown() ``` - Category: - Client management + posthog-capability: shutdown """ _proxy("shutdown") @@ -1422,8 +1404,7 @@ def setup() -> Client: ``project_api_key`` are missing or blank, the client is disabled and module-level calls become no-ops. - Category: - Initialization + posthog-capability: setup """ global default_client if not default_client: diff --git a/posthog/async_client.py b/posthog/async_client.py index 72f7cd2fe..ce756ffa3 100644 --- a/posthog/async_client.py +++ b/posthog/async_client.py @@ -138,6 +138,7 @@ def __init__( email_request_timeout_seconds: float = 15, email_request_max_retries: int = 2, ) -> None: + """posthog-capability: setup""" if flush_at <= 0: raise ValueError("flush_at must be greater than zero") if flush_interval <= 0: @@ -283,6 +284,8 @@ def emails(self) -> AsyncEmails: distinct_id="user_123", ) ``` + + posthog-capability: emails """ if self._emails is None: self._emails = _client_emails( @@ -567,7 +570,10 @@ def _build_capture_event( def capture( self, event: str, **kwargs: Unpack[OptionalCaptureArgs] ) -> Optional[str]: - """Queue an event without blocking for network delivery.""" + """Queue an event without blocking for network delivery. + + posthog-capability: capture + """ try: msg, disable_geoip, property_allowlist = self._build_capture_event( event, kwargs @@ -607,7 +613,10 @@ def _finish_inflight_operation(self, completion: asyncio.Future[None]) -> None: async def capture_immediate( self, event: str, **kwargs: Unpack[OptionalCaptureArgs] ) -> Optional[str]: - """Capture one event and wait until its delivery attempt completes.""" + """Capture one event and wait until its delivery attempt completes. + + posthog-capability: capture + """ current = asyncio.current_task() if current is None: # pragma: no cover - async functions always have a task return None @@ -690,6 +699,7 @@ def _build_person_properties_event( } def set(self, **kwargs: Unpack[OptionalSetArgs]) -> Optional[str]: + """posthog-capability: identify""" try: msg = self._build_person_properties_event("$set", "$set", kwargs) if msg is None: @@ -702,6 +712,7 @@ def set(self, **kwargs: Unpack[OptionalSetArgs]) -> Optional[str]: return None def set_once(self, **kwargs: Unpack[OptionalSetArgs]) -> Optional[str]: + """posthog-capability: identify""" try: msg = self._build_person_properties_event("$set_once", "$set_once", kwargs) if msg is None: @@ -723,6 +734,7 @@ def group_identify( disable_geoip: Optional[bool] = None, distinct_id: Optional[ID_TYPES] = None, ) -> Optional[str]: + """posthog-capability: group-identify""" try: if not _stringify_id(group_type): self.log.warning( @@ -765,6 +777,7 @@ def alias( uuid: Optional[str] = None, disable_geoip: Optional[bool] = None, ) -> Optional[str]: + """posthog-capability: alias""" try: resolved_previous_id = _stringify_id(previous_id) if not resolved_previous_id: @@ -812,7 +825,10 @@ def capture_exception( exception: Optional[ExceptionArg] = None, **kwargs: Unpack[OptionalCaptureArgs], ) -> Optional[str]: - """Capture an exception. This method never raises, including in debug mode.""" + """Capture an exception. This method never raises, including in debug mode. + + posthog-capability: capture-exception + """ try: if exception is not None and exception_is_already_captured(exception): self.log.debug("Exception already captured, skipping") @@ -957,6 +973,8 @@ async def evaluate_flags( ``capture(flags=...)`` to attach the exact values used for branching. This client does not evaluate flags locally, so the snapshot's ``unresolved_flags`` is always empty. + + posthog-capability: evaluate-flags """ host = self._get_feature_flag_evaluations_host() if distinct_id is None: @@ -1184,6 +1202,7 @@ async def wait_for_workers() -> None: await asyncio.gather(queue_join, workers_finished, return_exceptions=True) async def flush(self, timeout_seconds: Optional[float] = 10) -> None: + """posthog-capability: flush""" if asyncio.current_task() in self._worker_tasks or _is_processing_event(): self._defer_lifecycle_call(self.flush(timeout_seconds)) return @@ -1214,6 +1233,7 @@ async def _close_transport(self) -> None: await http_client.aclose() async def shutdown(self) -> None: + """posthog-capability: shutdown""" current = asyncio.current_task() if ( current in self._worker_tasks @@ -1283,6 +1303,7 @@ async def shutdown(self) -> None: raise errors[0] async def join(self) -> None: + """posthog-capability: shutdown""" await self.shutdown() diff --git a/posthog/capture_compression.py b/posthog/capture_compression.py index 659c12fab..7b6760d06 100644 --- a/posthog/capture_compression.py +++ b/posthog/capture_compression.py @@ -30,6 +30,12 @@ class CaptureCompression(str, Enum): server decodes (``br`` is accepted by the server too but is intentionally left out for now). Inheriting from ``str`` keeps the members comparable to and serializable as their token values. + + Values: + NONE: Sends the body uncompressed. + GZIP: gzip, from the standard library. + DEFLATE: zlib (RFC 1950), from the standard library. + ZSTD: Faster and smaller, but needs ``pip install posthog[zstd]``. """ NONE = "none" diff --git a/posthog/client.py b/posthog/client.py index ce5eb3352..2776fb4a6 100644 --- a/posthog/client.py +++ b/posthog/client.py @@ -884,8 +884,7 @@ def __init__( posthog = Posthog('', host='') ``` - Category: - Initialization + posthog-capability: setup """ # api_key: This should be the Team API Key (token), public self.api_key = (project_api_key or "").strip() @@ -1274,6 +1273,8 @@ def emails(self) -> Emails: distinct_id="user_123", ) ``` + + posthog-capability: emails """ if self._emails is None: self._emails = _client_emails( @@ -1518,8 +1519,7 @@ def get_feature_variants( only these flags will be evaluated, improving performance. device_id: The device ID for this request. - Category: - Feature flags + posthog-capability: get-feature-flags """ resp_data = self.get_flags_decision( distinct_id, @@ -1605,8 +1605,7 @@ def get_feature_flags_and_payloads( result = posthog.get_feature_flags_and_payloads('') ``` - Category: - Feature flags + posthog-capability: get-feature-flags-and-payloads """ resp = self.get_flags_decision( distinct_id, @@ -1791,8 +1790,7 @@ def capture( posthog.capture('$pageview', distinct_id="distinct_id_of_the_user", properties={'$current_url': 'https://example.com'}) ``` - Category: - Capture + posthog-capability: capture """ return self._capture(event, self._analytics_lane, **kwargs) @@ -1812,8 +1810,7 @@ def capture_ai( size cap, posting to the dedicated AI ingestion endpoint. The payload is sent as given — no redaction or truncation is applied here. - Category: - Capture + posthog-capability: capture-ai """ if not event.startswith("$ai_"): self.log.debug( @@ -2016,8 +2013,7 @@ def set(self, **kwargs: Unpack[OptionalSetArgs]) -> Optional[str]: posthog.set(distinct_id='user123', properties={'name': 'Max Hedgehog'}) ``` - Category: - Identification + posthog-capability: identify Note: This method will not raise exceptions. Errors are logged. """ @@ -2066,8 +2062,7 @@ def set_once(self, **kwargs: Unpack[OptionalSetArgs]) -> Optional[str]: posthog.set_once(distinct_id='user123', properties={'initial_signup_date': '2024-01-01'}) ``` - Category: - Identification + posthog-capability: identify Note: This method will not raise exceptions. Errors are logged. """ @@ -2131,8 +2126,7 @@ def group_identify( }) ``` - Category: - Identification + posthog-capability: group-identify Note: This method will not raise exceptions. Errors are logged. """ @@ -2201,8 +2195,7 @@ def alias( posthog.alias(previous_id='distinct_id', distinct_id='alias_id') ``` - Category: - Identification + posthog-capability: alias Note: This method will not raise exceptions. Errors are logged. """ @@ -2269,8 +2262,7 @@ def capture_exception( posthog.capture_exception(e, 'user_distinct_id', properties=additional_properties) ``` - Category: - Error Tracking + posthog-capability: capture-exception """ distinct_id = kwargs.get("distinct_id", None) properties = kwargs.get("properties", None) @@ -2844,8 +2836,7 @@ def start_span( outgoing_headers = {"traceparent": span.traceparent()} ``` - Category: - Tracing + posthog-capability: traces """ pipeline = self._traces_pipeline if pipeline is None: @@ -2874,8 +2865,7 @@ def get_active_span(self) -> Optional[Span]: headers["traceparent"] = span.traceparent() ``` - Category: - Tracing + posthog-capability: traces """ return self._active_span_var.get() @@ -2917,6 +2907,8 @@ def flush(self, timeout_seconds: Optional[float] = 10) -> None: # Before a serverless invocation returns; keep the shared client open. posthog.flush(timeout_seconds=10) ``` + + posthog-capability: flush """ include_metrics_autocapture = not self._skip_metrics_autocapture_flush.get() if self._defer_flush_from_callback( @@ -3360,6 +3352,8 @@ def join(self) -> None: ```python posthog.join() ``` + + posthog-capability: shutdown """ with self._lifecycle_lock: self._join_requested = True @@ -3399,6 +3393,8 @@ def shutdown(self) -> None: ```python posthog.shutdown() ``` + + posthog-capability: shutdown """ with self._lifecycle_lock: if self._shutdown_complete: @@ -3835,8 +3831,7 @@ def load_feature_flags(self): posthog.load_feature_flags() ``` - Category: - Feature flags + posthog-capability: flag-definition-loader """ if self.disabled: with self._flag_definition_publication_lock: @@ -4020,8 +4015,7 @@ def feature_enabled( matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user') ``` - Category: - Feature flags + posthog-capability: is-feature-enabled """ warnings.warn( "`feature_enabled` is deprecated and will be removed in a future major version. " @@ -4312,6 +4306,8 @@ def get_feature_flag_result( Returns: Optional[FeatureFlagResult]: The feature flag result or None if disabled/not found. + + posthog-capability: get-feature-flag-result """ return self._get_feature_flag_result( key, @@ -4361,8 +4357,7 @@ def get_feature_flag( matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user') ``` - Category: - Feature flags + posthog-capability: get-feature-flag """ warnings.warn( "`get_feature_flag` is deprecated and will be removed in a future major version. " @@ -4470,8 +4465,7 @@ def get_feature_flag_payload( matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user') ``` - Category: - Feature flags + posthog-capability: get-feature-flag-payload """ warnings.warn( "`get_feature_flag_payload` is deprecated and will be removed in a future major " @@ -4778,8 +4772,7 @@ def get_all_flags( posthog.get_all_flags('distinct_id_of_your_user') ``` - Category: - Feature flags + posthog-capability: get-feature-flags """ response = self.get_all_flags_and_payloads( distinct_id, @@ -4825,8 +4818,7 @@ def get_all_flags_and_payloads( posthog.get_all_flags_and_payloads('distinct_id_of_your_user') ``` - Category: - Feature flags + posthog-capability: get-feature-flags-and-payloads """ if self.disabled: return {"featureFlags": None, "featureFlagPayloads": None} @@ -4939,8 +4931,7 @@ def evaluate_flags( posthog.capture("page_viewed", distinct_id="user_123", flags=flags) ``` - Category: - Feature flags + posthog-capability: evaluate-flags """ host = self._get_feature_flag_evaluations_host() @@ -5266,8 +5257,7 @@ def feature_flag_definitions(self): The currently loaded feature flag definitions, or ``None`` before local evaluation has loaded definitions. - Category: - Feature flags + posthog-capability: flag-definition-loader """ return self.feature_flags diff --git a/posthog/emails.py b/posthog/emails.py index c5bb6cec8..4b856ddcf 100644 --- a/posthog/emails.py +++ b/posthog/emails.py @@ -125,6 +125,17 @@ class EmailError(Exception): Branch on ``code``, PostHog's stable error code, or on ``name``, a coarse category of the failure. The message never contains the secret key or the request body. + + Attributes: + name: A coarse category of the failure. + code: PostHog's stable error code. + message: What went wrong. It never contains the secret key or the request body. + status_code: The HTTP status of the response, or ``None`` when none arrived. + why: PostHog's explanation of the failure, when it gave one. + fix: What to change to fix it, when PostHog said. + docs_url: A docs page about the error, when PostHog gave one. + field: The request field the error is about, when there is one. + retry_after: Seconds to wait before retrying, when PostHog said. """ def __init__( @@ -161,7 +172,14 @@ class EmailWarning: @dataclass(frozen=True) class SendEmailResult: - """The queued email. ``id`` is its message ID.""" + """The queued email. ``id`` is its message ID. + + Attributes: + id: The email's message ID. + replayed: ``True`` when the response says PostHog replayed an earlier send for + the same ``idempotency_key`` instead of sending again. + warnings: Warnings PostHog returned about the request. + """ id: str replayed: bool = False @@ -185,6 +203,13 @@ class EmailStatus: ``reason`` says why a failed email was not sent, such as ``provider_error`` or ``recipient_suppressed``, or gives the bounce type of a bounced email: ``hard``, ``soft`` or ``unknown``. It is ``None`` otherwise. + + Attributes: + id: The email's message ID. + status: Where the email is now, one of the values above. + created_at: When PostHog queued the email. + status_at: When the email reached its current status. + reason: Why a failed email was not sent, or a bounced email's bounce type. """ id: str diff --git a/posthog/feature_flags.py b/posthog/feature_flags.py index 6a16ee319..9db7634bc 100644 --- a/posthog/feature_flags.py +++ b/posthog/feature_flags.py @@ -99,6 +99,9 @@ class InconclusiveMatchError(Exception): """ Raised when local evaluation can't determine a flag's value. ``reason`` says why, as reported by ``FeatureFlagEvaluations.unresolved_flags``. + + Attributes: + reason: Why local evaluation couldn't resolve the flag. """ reason: UnresolvedFlagReason = UnresolvedFlagReason.UNSUPPORTED_DEFINITION @@ -116,6 +119,9 @@ class RequiresServerEvaluation(Exception): This error should propagate immediately to trigger API fallback, unlike InconclusiveMatchError which allows trying other conditions. + + Attributes: + reason: Why the flag needs server-side evaluation. """ reason: UnresolvedFlagReason = UnresolvedFlagReason.UNSUPPORTED_DEFINITION diff --git a/posthog/types.py b/posthog/types.py index 85938a939..6aa7a2a0c 100644 --- a/posthog/types.py +++ b/posthog/types.py @@ -84,6 +84,11 @@ class FeatureFlagEvaluationRuntime(str, Enum): ``ALL`` means the flag suits both client-side and server-side evaluation, so it matches either runtime. Inheriting from ``str`` keeps the members directly comparable to their ``"all"`` / ``"client"`` / ``"server"`` values. + + Values: + ALL: The flag suits both client-side and server-side evaluation. + CLIENT: The flag is meant for client-side evaluation. + SERVER: The flag is meant for server-side evaluation. """ ALL = "all" @@ -278,6 +283,7 @@ def __post_init__(self) -> None: ) def get_value(self) -> FlagValue: + """Return the variant for a multivariate flag, otherwise whether the flag is enabled.""" return self.variant or self.enabled @classmethod @@ -294,6 +300,7 @@ def from_json(cls, resp: Any) -> "FeatureFlag": def from_value_and_payload( cls, key: str, value: FlagValue, payload: Any ) -> "FeatureFlag": + """Build a flag from its value and payload. A string value is a variant, so the flag is enabled.""" enabled, variant = (True, value) if isinstance(value, str) else (value, None) return cls( key=key, @@ -542,7 +549,12 @@ class FlagsResponse(TypedDict, total=False): class FlagsAndPayloads(TypedDict, total=True): - """Feature flag values and payloads keyed by feature flag key.""" + """Feature flag values and payloads keyed by feature flag key. + + Attributes: + featureFlags: Each flag's value, keyed by flag key. + featureFlagPayloads: Each flag's payload, keyed by flag key. + """ featureFlags: Optional[dict[str, FlagValue]] featureFlagPayloads: Optional[dict[str, Any]] From b5a6328044e2d5a9ff638492a583119b581fddc0 Mon Sep 17 00:00:00 2001 From: "Vincent (Wen Yu) Ge" Date: Wed, 4 Nov 2026 14:53:53 -0500 Subject: [PATCH 2/8] docs: render the reference in one Python script Drops the Node renderer, its tests, the page check and the sdk-specs module. The generated pages are unchanged byte for byte. Co-Authored-By: Claude Opus 5.5 --- bin/docs_scripts/check_reference.py | 195 -------- bin/docs_scripts/generate_markdown.py | 688 +++++++++++++------------- bin/docs_scripts/render.mjs | 200 -------- bin/docs_scripts/render.test.mjs | 167 ------- bin/docs_scripts/sdk_specs.py | 43 -- 5 files changed, 345 insertions(+), 948 deletions(-) delete mode 100644 bin/docs_scripts/check_reference.py delete mode 100644 bin/docs_scripts/render.mjs delete mode 100644 bin/docs_scripts/render.test.mjs delete mode 100644 bin/docs_scripts/sdk_specs.py diff --git a/bin/docs_scripts/check_reference.py b/bin/docs_scripts/check_reference.py deleted file mode 100644 index e96540e02..000000000 --- a/bin/docs_scripts/check_reference.py +++ /dev/null @@ -1,195 +0,0 @@ -"""End condition for the Python reference page. - -Builds its own inventory of the public Python surface with `ast`, independent of the extractor, -then checks the generated page against it and against the release rules: - - python bin/docs_scripts/check_reference.py - -Exits 1 and lists every failure. -""" - -import ast -import re -import sys -from pathlib import Path - -sys.path.insert(0, str(Path(__file__).resolve().parent)) -import sdk_specs # noqa: E402 - -ROOT = Path(__file__).resolve().parents[2] - -STRUCTURE = {"Getting started", "SDK utils", "Configs", "Types"} -BANNED = { - "Used by section": r"^(#+ |\*\*)?Used by\b[:*]", - "preview wording": r"\b(preview|candidate)\b", - "commit SHA": r"\b[0-9a-f]{12,40}\b", - "empty class stub": r"^class \w+\(\):\s*$", - "scraped icon text": r"svg/svg", - "Sphinx role": r":(meth|func|class|attr|obj|data|exc|mod):`", - "Sphinx literal block": r"::$", - "variadic default": r"\*\*\w+[^,)\n]* = \{\}", - "private parameter": r"^- `_\w+`", - "old example label": r"\*\*(Good|Bad) example", - "unlabeled example": r"\*\*Example\.\*\*", - "list instead of table": r"^- `\w+` \(", -} - - -def public_defs(path: Path, class_name: str | None) -> list[str]: - tree = ast.parse(path.read_text()) - body = tree.body - if class_name: - body = next( - n for n in tree.body if isinstance(n, ast.ClassDef) and n.name == class_name - ).body - names = [ - n.name for n in body if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef)) - ] - return sorted( - {n for n in names if not n.startswith("_")} | ({"__init__"} & set(names)) - ) - - -def slugify(text: str) -> str: - return re.sub(r"[^\w\- ]", "", text.lower()).replace(" ", "-") - - -def main(md_path: str, specs: str) -> int: - root = ROOT - md = Path(md_path).read_text() - caps = sdk_specs.capabilities(specs) - failures = [] - headings = set(re.findall(r"^#{3,4} `([^`]+)`$", md, re.M)) - - inventory = { - "Client": public_defs(root / "posthog/client.py", "Client"), - "AsyncClient": public_defs(root / "posthog/async_client.py", "AsyncClient"), - "posthog": public_defs(root / "posthog/__init__.py", None), - } - for owner, names in inventory.items(): - for name in names: - label = ( - f"{owner}({owner == 'posthog' and '' or '...'})" - if name == "__init__" - else f"{owner}.{name}" - ) - if name == "__init__": - label = f"{owner}(...)" - if label not in headings: - failures.append(f"missing public symbol {label}") - - status = re.search(r"^status: (current|experimental)$", md, re.M) - if not status: - failures.append("front matter has no current or experimental status") - h2 = re.findall(r"^## (.+)$", md, re.M) - allowed = ( - STRUCTURE - | {c["title"] for c in caps["capabilities"].values()} - | {"AsyncClient", "Module functions"} - ) - failures += [f"unexpected top-level heading {h}" for h in h2 if h not in allowed] - - blocks = len( - re.findall( - r"^(Parameters|Fields|Settings) \(\d+\)$", md, re.M - ) - ) - if blocks != md.count("
"): - failures.append( - f"{blocks} member blocks but {md.count('
')} details blocks" - ) - tables = len( - re.findall( - r"^(Parameters|Fields|Settings) \(\d+\)\n\n\| (Parameter|Field|Setting) \|\n\| --- \|$", - md, - re.M, - ) - ) - if tables != blocks: - failures.append( - f"{blocks} member blocks but only {tables} open with a one-column table" - ) - if re.search(r"
", md) or re.search( - r"^#{3,6} `\w+` (parameter|field|setting)$", md, re.M - ): - failures.append("a bare
or a member heading is left on the page") - # Replay the site's heading slugger: every capability heading must keep its plain sdk-specs anchor. - seen: dict[str, int] = {} - ids: list[tuple[str, str, str]] = [] - in_fence = False - for line in md.split("\n"): - if line.lstrip().startswith("```"): - in_fence = not in_fence - heading = None if in_fence else re.match(r"^(#{1,6}) (.+)$", line) - if heading: - base = slugify(heading.group(2)) - n = seen.get(base, 0) - seen[base] = n + 1 - ids.append( - (heading.group(1), heading.group(2), base if n == 0 else f"{base}-{n}") - ) - for level, text, anchor in ids: - cap = caps["capabilities"].get(slugify(text)) - if level == "##" and cap and cap["title"] == text and anchor != slugify(text): - failures.append(f"capability {text!r} lost its anchor, got #{anchor}") - failures += [ - f"escaped pipe inside code span: {s!r}" - for s in re.findall(r"`([^`\n]+)`", md) - if "\\|" in s - ] - for block in re.findall( - r"Fields \(\d+\).*?
", md, re.S - ): - failures += [ - f"field default is a name, not a value: {d!r}" - for d in re.findall(r"\*\*Default:\*\* `([^`]+)`", block) - if re.fullmatch(r"[a-z_][\w.]*( or .+)?", d) - and d not in ("None", "True", "False") - ] - for entry in re.split(r"^(?=#{3,4} `)", md, flags=re.M): - if ( - entry.startswith(("### `AsyncClient", "#### `AsyncClient")) - and "**Available in:**" not in entry.split("\n#", 1)[0] - ): - failures.append( - f"{entry.splitlines()[0]} doesn't say it needs the async extra" - ) - types = md.split("## Types", 1)[-1] - for name in ( - "CaptureCompression", - "FeatureFlagEvaluationRuntime", - "UnresolvedFlagReason", - ): - section = types.split(f"### `{name}`", 1)[-1].split("\n### ", 1)[0] - if f"### `{name}`" in types and "**Value:**" not in section: - failures.append(f"enum {name} doesn't show its member values") - failures += [ - f"type method {m} has no description" - for m in re.findall( - r"^#### `([^`]+)`\n\n(?:```|
|\*\*Returns|\[Source)", types, re.M - ) - ] - for block in re.findall( - r"Fields \(\d+\)(.*?)
", types, re.S - ): - failures += [ - f"type field {r.split('`')[1]} has no description" - for r in re.findall(r"^\| (`.+) \|$", block, re.M) - if not re.search(r"
(?!\*\*)[^<]", r) - ] - prose = re.sub(r"```.*?```", "", md, flags=re.S) - for name, pattern in BANNED.items(): - for match in re.finditer(pattern, prose, re.M | re.I): - failures.append(f"{name}: {match.group(0)!r}") - - for failure in failures: - print(f"FAIL {failure}") - total = sum(len(v) for v in inventory.values()) - print( - f"{len(failures)} failures, {total} public symbols checked, {len(h2)} top-level headings" - ) - return 1 if failures else 0 - - -if __name__ == "__main__": - sys.exit(main(*sys.argv[1:])) diff --git a/bin/docs_scripts/generate_markdown.py b/bin/docs_scripts/generate_markdown.py index 9407e5324..1a4bb41e1 100644 --- a/bin/docs_scripts/generate_markdown.py +++ b/bin/docs_scripts/generate_markdown.py @@ -1,35 +1,27 @@ -"""Generate this SDK's reference Markdown from source. +"""Generate the SDK reference Markdown from source with Griffe, which never imports the SDK. -Griffe reads the package statically, so the SDK is never imported. Docstrings are parsed as -Google style. A `posthog-capability: ` line maps a symbol to its sdk-specs capability, -and unmapped symbols land in SDK utils under their `Category:`. The shared renderer turns the -symbol list into Markdown, so every SDK's page has the same shape. +A `posthog-capability: ` docstring line puts a symbol under that PostHog/sdk-specs +capability, in the sdk-specs README's order. Untagged symbols go to SDK utils, grouped by +their `Category:`. - python bin/docs_scripts/generate_markdown.py --sdk-specs [--major 8 --source-ref v1] + python bin/docs_scripts/generate_markdown.py --sdk-specs DIR [--major 8 --source-ref v1] """ import argparse -import json import logging import re -import subprocess -import sys -import tempfile import textwrap from pathlib import Path -from typing import Any +from typing import NamedTuple import griffe -sys.path.insert(0, str(Path(__file__).resolve().parent)) -import sdk_specs # noqa: E402 - ROOT = Path(__file__).resolve().parents[2] -SOURCE = "https://github.com/PostHog/posthog-python/blob/v{version}/{path}#L{line}" -REFERENCES = [ - ("Client", "client.Client", "Client"), - ("AsyncClient", "async_client.AsyncClient", "AsyncClient"), -] +SOURCE = "[Source](https://github.com/PostHog/posthog-python/blob/{ref}/{path}#L{line})" +PAGE = "---\ntitle: PostHog Python SDK\nsdk: python\nversion: {}\nmajor: {}\nstatus: {}\n---" +GUIDES = ( + "---\ntitle: PostHog Python SDK migration guides\nsdk: python\nkind: migration\n---" +) TYPES = """args.OptionalCaptureArgs args.OptionalSetArgs feature_flag_evaluations.FeatureFlagEvaluations types.FeatureFlagResult types.FeatureFlag types.FlagsAndPayloads types.FeatureFlagEvaluationRuntime types.UnresolvedFlagReason flag_definition_cache.FlagDefinitionCacheProvider flag_definition_cache.FlagDefinitionCacheData @@ -46,7 +38,7 @@ } WRAPPERS = "openai anthropic gemini langchain openai_agents claude_agent_sdk".split() CONFIGS = [("integrations.django.PosthogContextMiddleware", "Django settings")] -# Where a symbol works when that isn't everywhere Python runs, by module. Extras are checked against pyproject.toml. +# Where a symbol works, by module, when that isn't everywhere Python runs. AVAILABLE_IN = { "async_client": "the `async` extra, `posthog[async]`", "integrations.django": "Django apps", @@ -62,229 +54,250 @@ "ai.openai_agents": "apps with the `openai-agents` package", "ai.claude_agent_sdk": "apps with the `claude-agent-sdk` package", } -LITERAL_VALUE = re.compile( - r"""^(None|True|False|-?\d[\d_.e]*|(["']).*\2|\[\]|\{\}|\(\))$""" -) +ACRONYMS = {"id": "ID", "ai": "AI", "http": "HTTP", "mcp": "MCP"} +STAR = {"variadic positional": "*", "variadic keyword": "**"} TAG = re.compile(r"^[ \t]*posthog-capability:[ \t]*([a-z0-9-]+)[ \t]*$\n?", re.M) +CAPABILITY_ROW = re.compile( + r"^\| ([^|]+?) \| \[`openspec/specs/([a-z0-9-]+)/spec\.md`\]", re.M +) FENCE = re.compile(r"```(\w*)\n(.*?)```", re.S) LABEL = re.compile(r"#\s*(Correct example|Counter example \(incorrect\))\.?:?\s*(.*)") ROLE = re.compile( r":(?:py:)?(?:meth|func|class|attr|obj|data|exc|mod|const):`~?([^`]+)`" ) LITERAL = re.compile(r"^([^\n]*?)::\n\n((?:(?:[ \t]+[^\n]*)?\n)+)", re.M) +LITERAL_VALUE = re.compile( + r"""^(None|True|False|-?\d[\d_.e]*|(["']).*\2|\[\]|\{\}|\(\))$""" +) +SETTING = re.compile(r"^\s*- `(POSTHOG_[A-Z_]+)`: (.*?)(?=^\s*- `|^\s*$)", re.M | re.S) +VALUE = re.compile(r"^(\w+): (.*?)(?=^\w+:|\Z)", re.M | re.S) +NEWLINE = re.compile(r"\s*\n\s*") + + +class Entry(NamedTuple): + slug: str | None + group: str | None + name: str + body: list[str] + + def render(self, level: int) -> str: + return "\n\n".join([f"{'#' * level} `{self.name}`", *self.body]) def text(value) -> str: - return str(value) if value is not None else "" + return "" if value is None else str(value) + + +def get(obj, path: str): + try: + return obj[path] + except KeyError: + return None + + +def fence(code: str) -> str: + return f"```python\n{code.strip()}\n```" + + +def code(value) -> str: + return f"`{NEWLINE.sub(' ', str(value)).strip()}`" if value else "" def md(value: str) -> str: - """Converts the Sphinx markup in these docstrings to Markdown: roles, double backticks, literal blocks.""" + """Sphinx roles, double backticks and literal blocks become Markdown.""" value = re.sub(r"``([^`]+)``", r"`\1`", ROLE.sub(r"`\1`", value or "")) def block(m: re.Match) -> str: lead = m.group(1) + ":\n\n" if m.group(1).strip() else "" - return lead + "```python\n" + textwrap.dedent(m.group(2)).strip() + "\n```\n\n" + return lead + fence(textwrap.dedent(m.group(2))) + "\n\n" return LITERAL.sub(block, value + "\n").strip() -def get(pkg, path: str): - try: - return pkg[path] - except KeyError: - return None +def unpipe(span: str) -> str: + """Escapes pipes for a table. A pipe splits its code span, since MDX shows `\\|` literally.""" + if not span.startswith("`"): + return span.replace("|", "\\|") + return " \\| ".join(f"`{x}`" for x in re.split(r"\s*\|\s*", span[1:-1])) -def available_in(path: str) -> str | None: - module = path.removeprefix("posthog.") - return next( - ( - where - for prefix, where in AVAILABLE_IN.items() - if module == prefix or module.startswith(prefix + ".") - ), - None, +def cell(value) -> str: + """Table cell text: fences become inline code and paragraphs join with
.""" + value = FENCE.sub( + lambda m: f"`{NEWLINE.sub('; ', m.group(2).strip())}`", text(value).strip() ) + value = "
".join(NEWLINE.sub(" ", p) for p in re.split(r"\n\s*\n", value)) + return "".join(unpipe(s) for s in re.split(r"(`[^`]*`)", value)) -def check_extras() -> None: - extras = re.search( - r"^\[project\.optional-dependencies\]\n(.*?)(?=^\[)", - (ROOT / "pyproject.toml").read_text(), - re.M | re.S, +def row(name, desc="", kind="", default="", required=False, value="", where="") -> str: + """One parameter, field or setting: its name, a labels line, then its description.""" + default = ( + "**Required**" + if required + else default and f"**Default:** {cell(code(default))}" ) - named = set(re.findall(r"^(\w+) = ", extras.group(1), re.M)) if extras else set() - missing = { - x for w in AVAILABLE_IN.values() for x in re.findall(r"posthog\[(\w+)\]", w) - } - named - if missing: - raise SystemExit( - f"AVAILABLE_IN names extras pyproject.toml doesn't define: {sorted(missing)}" - ) + labels = [ + where and f"**Where:** {where}", + value and f"**Value:** {cell(code(value))}", + kind and f"**Type:** {cell(code(kind))}", + default, + ] + parts = [code(name), " · ".join(x for x in labels if x), cell(desc)] + return "| " + "
".join(x for x in parts if x) + " |" -def source(obj, version: str) -> str: - return SOURCE.format( - version=version, path=obj.relative_package_filepath, line=obj.lineno - ) +def table(title: str, rows: list[str]) -> list[str]: + """A details block holding a one-column table.""" + head = f"{title} ({len(rows)})\n\n| {title[:-1]} |\n| --- |" + return ["\n".join(["
", head, *rows, "", "
"])] if rows else [] def parse(obj) -> tuple[str | None, list]: + """The symbol's capability tag and its parsed docstring sections.""" raw = obj.docstring.value if obj.docstring else "" - tag = TAG.search(raw) - body = TAG.sub("", raw).strip() + tag, body = TAG.search(raw), TAG.sub("", raw).strip() sections = griffe.Docstring(body, parent=obj).parse("google") if body else [] return (tag.group(1) if tag else None), sections -def examples(value) -> list[dict]: +def examples(blocks: str) -> list[str]: + """Each fenced block, labeled by its first comment line. Unlabeled blocks are correct.""" out = [] - for _, code in FENCE.findall("\n".join(t for _, t in value)): - label = why = None - head, _, rest = code.lstrip("\n").partition("\n") - match = LABEL.match(head.strip()) - if match: - label, why, code = ( - "correct" if match.group(1).startswith("Correct") else "incorrect", - match.group(2) or None, - rest, - ) - out.append({"code": code, "label": label, "why": why}) + for _, src in FENCE.findall(blocks): + head, _, rest = src.lstrip("\n").partition("\n") + label = "**Correct example.**" + if m := LABEL.match(head.strip()): + src, why = rest, m.group(2) + if m.group(1).startswith("Counter"): + why = ( + why[:1].upper() + + why[1:] + + ("." if why and why[-1] not in ".!?" else "") + ) + label = f"**Counter example (incorrect).** {why}".strip() + out.append(f"{label}\n\n{fence(src)}") return out -def typed_dict_fields(pkg, annotation: str) -> dict: - match = re.fullmatch(r"Unpack\[(\w+)\]", annotation) - cls = get(pkg, f"args.{match.group(1)}") if match else None - return {n: text(a.annotation) for n, a in cls.attributes.items()} if cls else {} - - -def params(pkg, fn, documented: dict) -> list[dict]: - out = [] - for p in fn.parameters: - if p.name in ("self", "cls") or p.name.startswith("_"): - continue - kind = p.kind.value - if kind == "variadic keyword": - fields = typed_dict_fields(pkg, text(p.annotation)) - for name in [n for n in documented if n in fields]: - out.append( - { - "name": name, - "type": fields[name], - "description": md(documented.pop(name)), - } - ) - if fields: - continue - star = {"variadic positional": "*", "variadic keyword": "**"}.get(kind, "") - required = p.default is None and not star - out.append( - { - "name": star + p.name, - "type": text(p.annotation), - "default": None if star else p.default and text(p.default), - "required": required, - "description": md(documented.pop(p.name, "")), - } - ) - out += [{"name": n, "description": md(d)} for n, d in documented.items()] - return out +def public(p) -> bool: + return p.name not in ("self", "cls") and not p.name.startswith("_") def signature(fn, name: str) -> str: parts = [] - for p in fn.parameters: - if p.name in ("self", "cls") or p.name.startswith("_"): - continue - star = {"variadic positional": "*", "variadic keyword": "**"}.get( - p.kind.value, "" - ) + for p in filter(public, fn.parameters): + star = STAR.get(p.kind.value, "") part = star + p.name + (f": {p.annotation}" if p.annotation is not None else "") parts.append( part + (f" = {p.default}" if p.default is not None and not star else "") ) - ret = f" -> {fn.returns}" if fn.returns is not None and name[0].islower() else "" - prefix = "async " if "async" in fn.labels else "" - line = f"{prefix}{name}({', '.join(parts)}){ret}" - return ( - line - if len(line) <= 88 - else f"{prefix}{name}(\n" + "".join(f" {p},\n" for p in parts) + f"){ret}" + returns = ( + f" -> {fn.returns}" if fn.returns is not None and name[0].islower() else "" ) + head = ("async " if "async" in fn.labels else "") + name + if len(line := f"{head}({', '.join(parts)}){returns}") <= 88: + return line + return f"{head}(\n" + "".join(f" {p},\n" for p in parts) + f"){returns}" + + +def params(fn, documented: dict) -> list[str]: + """A row per parameter. Each documented key of an `Unpack[...]` TypedDict gets a row.""" + rows = [] + for p in filter(public, fn.parameters): + star = STAR.get(p.kind.value, "") + unpacked = re.fullmatch(r"Unpack\[(\w+)\]", text(p.annotation)) + typed = get(fn.package, f"args.{unpacked.group(1)}") if unpacked else None + if star == "**" and typed and (fields := typed.attributes): + for n in [n for n in documented if n in fields]: + rows.append(row(n, md(documented.pop(n)), text(fields[n].annotation))) + continue + desc = md(documented.pop(p.name, "")) + default, required = ( + "" if star else text(p.default), + p.default is None and not star, + ) + rows.append(row(star + p.name, desc, text(p.annotation), default, required)) + return rows + [row(n, md(d)) for n, d in documented.items()] -def entry(pkg, obj, qualname: str, version: str, group: str | None = None) -> dict: +def entry(obj, name: str, ref: str, group: str | None = None) -> Entry: slug, sections = parse(obj) - e: dict[str, Any] = { - "qualname": qualname, - "capability": slug, - "group": group, - "description": "", - "examples": [], - "source": source(obj, version), - "availableIn": available_in(obj.module.path), - } - texts, notes, documented = [], [], {} + texts, notes, shown, raises = [], [], [], [] + documented: dict[str, str] = {} + returns, deprecated = ("", ""), "" for s in sections: - kind = s.kind.value + kind, value = s.kind.value, s.value if kind == "text": - texts.append(s.value) + texts.append(value) elif kind in ("parameters", "other parameters"): - documented.update({p.name: p.description for p in s.value}) + documented |= {p.name: p.description for p in value} elif kind == "returns": - e["returns"] = { - "type": text(s.value[0].annotation) - or text(getattr(obj, "returns", "")), - "description": md(" ".join(r.description for r in s.value)), - } + rtype = text(value[0].annotation) or text(getattr(obj, "returns", "")) + returns = (rtype, md(" ".join(r.description for r in value))) elif kind == "raises": - e["raises"] = [ - {"type": text(r.annotation), "description": md(r.description)} - for r in s.value + raises = [ + f"**Raises** {code(text(r.annotation))}. {md(r.description)}".strip() + for r in value ] elif kind == "examples": - e["examples"] += examples(s.value) + shown += examples("\n".join(t for _, t in value)) elif kind == "deprecated": - e["deprecated"] = s.value.description + deprecated = f"**Deprecated.** {value.description}" elif kind == "admonition" and s.title in ("Example", "Examples"): - body = s.value.description - e["examples"] += ( - examples([(None, body)]) + body = value.description + shown += ( + examples(body) if "```" in body - else [{"code": body, "label": None, "why": None}] + else [f"**Correct example.**\n\n{fence(body)}"] ) elif kind == "admonition" and s.title == "Category": - e["group"] = group or s.value.description.strip() + group = group or value.description.strip() elif kind == "admonition": - notes.append(f"**{s.title}.** {md(s.value.description)}") - e["description"], e["notes"] = md("\n\n".join(texts)), "\n\n".join(notes) + notes.append(f"**{s.title}.** {md(value.description)}") + sig, rows = "", [] if obj.is_function: - name = qualname.removesuffix("(...)") - e["signature"] = signature(obj, name if obj.name == "__init__" else obj.name) - e["params"] = params(pkg, obj, documented) - if not e.get("returns") and obj.returns is not None and obj.name != "__init__": - e["returns"] = {"type": text(obj.returns)} + sig = signature( + obj, name.removesuffix("(...)") if obj.name == "__init__" else obj.name + ) + rows = params(obj, documented) + if not any(returns) and obj.returns is not None and obj.name != "__init__": + returns = (text(obj.returns), "") elif obj.is_class and "__init__" in obj.members: init = obj.members["__init__"] - e["signature"] = signature(init, obj.name) - e["params"] = params( - pkg, - init, - documented - | { - p.name: p.description - for s in parse(init)[1] - if s.kind.value == "parameters" - for p in s.value - }, - ) - elif obj.is_attribute: - e["signature"] = ( - f"{qualname}: {obj.annotation}" if obj.annotation is not None else None - ) - return e + args = { + p.name: p.description + for s in parse(init)[1] + if s.kind.value == "parameters" + for p in s.value + } + sig, rows = signature(init, obj.name), params(init, documented | args) + elif obj.is_attribute and obj.annotation is not None: + sig = f"{name}: {obj.annotation}" + where = available_in(obj.module.path) + returned = any(returns) and f"**Returns** {code(returns[0])}" + ( + returns[1] and f". {returns[1]}" + ) + body = [ + deprecated, + where and f"**Available in:** {where}", + md("\n\n".join(texts)), + sig and fence(sig), + *table("Parameters", rows), + returned and returned.strip(), + *raises, + "\n\n".join(notes), + *shown, + SOURCE.format(ref=ref, path=obj.relative_package_filepath, line=obj.lineno), + ] + return Entry(slug, group, name, [b for b in body if b]) + + +def available_in(module: str) -> str | None: + module = module.removeprefix("posthog.") + "." + return next( + (w for m, w in AVAILABLE_IN.items() if module.startswith(m + ".")), None + ) def members(cls) -> list: @@ -297,126 +310,84 @@ def members(cls) -> list: ] -def documented_fields(cls) -> dict: - """Field descriptions from the class's `Attributes:`, `Args:` and `Values:` sections, and from `__init__`'s `Args:`.""" +def type_block(cls, ref: str) -> str: + """A type's section. Dataclasses, TypedDicts, enums and errors show their fields.""" sections = parse(cls)[1] + ( parse(cls.members["__init__"])[1] if "__init__" in cls.members else [] ) - found = { - item.name: item.description + documented = { + i.name: i.description for s in sections if s.kind.value in ("attributes", "parameters") - for item in s.value + for i in s.value } for s in sections: if s.kind.value == "admonition" and s.title == "Values": - found |= { - n: " ".join(d.split()) - for n, d in re.findall( - r"^(\w+): (.*?)(?=^\w+:|\Z)", s.value.description, re.M | re.S - ) + documented |= { + n: " ".join(d.split()) for n, d in VALUE.findall(s.value.description) } - return found - - -def is_data(cls) -> bool: - """Dataclasses, TypedDicts, enums and errors carry fields readers use. Service objects carry only state.""" bases = " ".join(text(b) for b in cls.bases) - return any("dataclass" in text(d.value) for d in cls.decorators) or any( + data = any("dataclass" in text(d.value) for d in cls.decorators) or any( k in bases for k in ("TypedDict", "Enum", "Exception", "Error") ) - - -def type_info(pkg, cls, version: str) -> dict: - _, sections = parse(cls) - documented = documented_fields(cls) - # Only a literal is a default. A field set from an __init__ argument shows that argument's name instead. - enum = any("Enum" in text(b) for b in cls.bases) - - def value(a): - return text(a.value) if LITERAL_VALUE.match(text(a.value)) else None - - fields = [ - { - "name": n, - "type": None if enum else text(a.annotation), - "default": None if enum else value(a), - "value": value(a) if enum else None, - "description": md( - a.docstring.value if a.docstring else documented.get(n, "") - ), - } - for n, a in cls.attributes.items() - if is_data(cls) and not n.startswith("_") and "property" not in a.labels - ] + rows = [] + for n, a in cls.attributes.items(): + if data and not n.startswith("_") and "property" not in a.labels: + # Only a literal is a default. A field set from an __init__ argument has none. + literal = text(a.value) if LITERAL_VALUE.match(text(a.value)) else "" + desc = md(a.docstring.value if a.docstring else documented.get(n, "")) + enum = "Enum" in bases + rows.append( + row(n, desc, value=literal) + if enum + else row(n, desc, text(a.annotation), literal) + ) methods = [ - entry(pkg, m, f"{cls.name}.{m.name}", version) + entry(m, f"{cls.name}.{m.name}", ref).render(4) for m in members(cls) if m.name != "__init__" ] - description = md("\n\n".join(s.value for s in sections if s.kind.value == "text")) - return { - "name": cls.name, - "description": description, - "fields": fields, - "methods": methods, - "source": source(cls, version), - } + desc = md("\n\n".join(s.value for s in parse(cls)[1] if s.kind.value == "text")) + if not (desc or rows or methods): + return "" + src = SOURCE.format(ref=ref, path=cls.relative_package_filepath, line=cls.lineno) + return "\n\n".join( + [ + f"### `{cls.name}`", + *filter(None, [desc]), + *table("Fields", rows), + *methods, + src, + ] + ) -def configs(pkg) -> list[dict]: - out = [] - for path, where in CONFIGS: - cls = get(pkg, path) - doc = cls.docstring.value if cls and cls.docstring else "" - for name, desc in re.findall( - r"^\s*- `(POSTHOG_[A-Z_]+)`: (.*?)(?=^\s*- `|^\s*$)", doc, re.M | re.S - ): - out.append( - { - "name": name, - "where": where, - "description": md(" ".join(desc.split())), - } - ) - return out +def reference(pkg, caps: dict[str, str], ref: str) -> list[str]: + """Every capability, SDK utils, AsyncClient, module functions, configs and types.""" + def methods(cls: str, prefix: str) -> list[Entry]: + names = {"__init__": "(...)"} + return [ + entry(m, prefix + names.get(m.name, f".{m.name}"), ref) + for m in members(pkg[cls]) + ] -def page(pkg, version: str, major: int, status: str) -> dict: - refs: list[dict[str, Any]] = [] - for title, path, prefix in REFERENCES: - cls = pkg[path] - refs.append( - { - "title": title, - "entries": [ - entry( - pkg, - m, - f"{prefix}(...)" - if m.name == "__init__" - else f"{prefix}.{m.name}", - version, - ) - for m in members(cls) - ], - } - ) + client, asyncs = ( + methods("client.Client", "Client"), + methods("async_client.AsyncClient", "AsyncClient"), + ) module = [ - entry(pkg, m, f"posthog.{n}", version) + entry(m, f"posthog.{n}", ref) for n, m in pkg.members.items() if not m.is_alias and m.is_function and not n.startswith("_") ] - refs.append({"title": "Module functions", "entries": module}) for group, paths in UTILS.items(): - for path in paths: - if (obj := get(pkg, path)) is not None: - refs[0]["entries"].append( - entry(pkg, obj, f"posthog.{path}", version, group) - ) + for obj, path in [(get(pkg, p), p) for p in paths]: + if obj is not None: + client.append(entry(obj, f"posthog.{path}", ref, group)) if obj.is_class: - refs[0]["entries"] += [ - entry(pkg, m, f"{obj.name}.{m.name}", version, group) + client += [ + entry(m, f"{obj.name}.{m.name}", ref, group) for m in members(obj) if m.name != "__init__" ] @@ -428,94 +399,125 @@ def page(pkg, version: str, major: int, status: str) -> dict: if not n.startswith("_") and n[0].isupper() ) summary = md(mod.docstring.value.split("\n\n")[0]) if mod.docstring else "" - refs[0]["entries"].append( - { - "qualname": f"posthog.ai.{name}", - "group": "AI wrapper clients", - "availableIn": available_in(f"ai.{name}"), - "description": " ".join( - filter(None, [summary, f"Exports {names}."]) - ), - } + where = available_in(f"ai.{name}") + body = [ + where and f"**Available in:** {where}", + " ".join(filter(None, [summary, f"Exports {names}."])), + ] + client.append( + Entry( + None, + "AI wrapper clients", + f"posthog.ai.{name}", + [b for b in body if b], + ) ) - # Newest guide first, so the migration page opens on the latest upgrade. - migrations = sorted((ROOT / "docs").glob("migration-*.md"), reverse=True) - return { - "sdk": "python", - "title": "PostHog Python SDK", - "version": version, - "major": major, - "status": status, - "language": "python", - "gettingStarted": (ROOT / "docs/getting-started.md").read_text(), - "references": refs, - "configs": configs(pkg), - "types": [type_info(pkg, c, version) for p in TYPES if (c := get(pkg, p))], - "migrations": [m.read_text() for m in migrations], - } + if unknown := {e.slug for e in client + asyncs + module if e.slug} - caps.keys(): + raise SystemExit( + f"posthog-capability tags sdk-specs doesn't define: {sorted(unknown)}" + ) + + # Each capability is titled as in sdk-specs, so its heading's anchor is the capability's slug. + out = [] + for slug, title in caps.items(): + if tagged := [e for e in client if e.slug == slug]: + out += [f"## {title}", *(e.render(3) for e in tagged)] + if utils := [e for e in client if not e.slug]: + out.append("## SDK utils") + for group in dict.fromkeys(e.group or "Other" for e in utils): + out += [ + f"### {group}", + *(e.render(4) for e in utils if (e.group or "Other") == group), + ] + for title, entries in (("AsyncClient", asyncs), ("Module functions", module)): + ordered = sorted( + entries, key=lambda e: list(caps).index(e.slug) if e.slug else len(caps) + ) + out += [f"## {title}", *(e.render(3) for e in ordered)] + settings = [] + for path, where in CONFIGS: + cls = get(pkg, path) + doc = cls.docstring.value if cls and cls.docstring else "" + settings += [ + row(n, md(" ".join(d.split())), where=where) + for n, d in SETTING.findall(doc) + ] + if settings: + out += ["## Configs", *table("Settings", settings)] + if types := [ + t for t in (type_block(pkg[p], ref) for p in TYPES if get(pkg, p)) if t + ]: + out += ["## Types", *types] + return out + + +def capabilities(checkout: str) -> dict[str, str]: + """Each sdk-specs capability's slug and title in sentence case, in the README's order.""" + caps = {} + for name, slug in CAPABILITY_ROW.findall( + (Path(checkout) / "README.md").read_text() + ): + words = [ + ACRONYMS.get(w.lower(), w if w.isupper() and len(w) > 1 else w.lower()) + for w in name.split() + ] + title = " ".join(words) + caps[slug] = title[0].upper() + title[1:] + return caps + + +def nest(markdown: str, drop_title: bool = True) -> str: + """Moves each heading down one level, dropping the H1 when asked. Code stays as written.""" + out, in_fence = [], False + for line in markdown.strip().split("\n"): + in_fence ^= bool(re.match(r"\s*(```|~~~)", line)) + if in_fence or not re.match(r"#{1,5} ", line): + out.append(line) + elif not (drop_title and line.startswith("# ")): + out.append("#" + line) + return "\n".join(out).lstrip("\n") + + +def write(name: str, sections: list[str]) -> None: + (ROOT / "references" / name).write_text("\n\n".join(sections) + "\n") + print(f"wrote references/{name}") def main() -> None: - args = argparse.ArgumentParser() - args.add_argument( - "--sdk-specs", - required=True, - help="Checkout of PostHog/sdk-specs to take capabilities from", - ) - args.add_argument( - "--renderer", default=str(Path(__file__).resolve().parent / "render.mjs") - ) + args = argparse.ArgumentParser(description="Generate the SDK reference Markdown.") + args.add_argument("--sdk-specs", required=True, help="PostHog/sdk-specs checkout") args.add_argument( - "--major", - type=int, - help="Major this branch targets, defaults to the declared version's major", + "--major", type=int, help="target major, defaults to the declared one" ) args.add_argument( - "--source-ref", - help="Git ref for source links, defaults to the release tag v", + "--source-ref", help="git ref for source links, defaults to the tag" ) opts = args.parse_args() logging.getLogger("griffe").setLevel(logging.ERROR) - check_extras() - pkg = griffe.load( - "posthog", - search_paths=[str(ROOT)], - docstring_parser="google", - resolve_aliases=False, - ) - found = re.search(r'VERSION = "([^"]+)"', (ROOT / "posthog/version.py").read_text()) - if found is None: - raise SystemExit("posthog/version.py has no VERSION") - declared = found.group(1) + pkg = griffe.load("posthog", search_paths=[str(ROOT)], resolve_aliases=False) + declared = (ROOT / "posthog/version.py").read_text().split('"')[1] major = opts.major or int(declared.split(".")[0]) - # A branch that targets a newer major than it has released documents an experimental reference. + # A branch that targets a newer major than it has released documents an experimental one. status = "current" if int(declared.split(".")[0]) >= major else "experimental" version = declared if status == "current" else "unreleased" - global SOURCE - SOURCE = SOURCE.replace("v{version}", opts.source_ref or "v{version}") - data = page(pkg, version, major, status) - outputs = { - f"posthog-python-{data['major']}.md": [], - "posthog-python-migration.md": ["--migration"], - } - with ( - tempfile.NamedTemporaryFile("w", suffix=".json") as f, - tempfile.NamedTemporaryFile("w", suffix=".json") as caps, - ): - json.dump(data, f) - json.dump(sdk_specs.capabilities(opts.sdk_specs), caps) - f.flush() - caps.flush() - for name, flag in outputs.items(): - md = subprocess.run( - ["node", opts.renderer, f.name, caps.name, *flag], - check=True, - capture_output=True, - text=True, - ).stdout - if md: - (ROOT / "references" / name).write_text(md) - print(f"wrote references/{name}") + ref = opts.source_ref or f"v{version}" + started = nest((ROOT / "docs/getting-started.md").read_text()) + reference_md = reference(pkg, capabilities(opts.sdk_specs), ref) + write( + f"posthog-python-{major}.md", + [ + PAGE.format(version, major, status), + "## Getting started", + started, + *reference_md, + ], + ) + # Newest guide first, so the migration page opens on the latest upgrade. + if guides := sorted((ROOT / "docs").glob("migration-*.md"), reverse=True): + write( + "posthog-python-migration.md", + [GUIDES, *(nest(g.read_text(), drop_title=False) for g in guides)], + ) if __name__ == "__main__": diff --git a/bin/docs_scripts/render.mjs b/bin/docs_scripts/render.mjs deleted file mode 100644 index aee720468..000000000 --- a/bin/docs_scripts/render.mjs +++ /dev/null @@ -1,200 +0,0 @@ -#!/usr/bin/env node -// Renders one SDK's symbol list into its reference page Markdown. -// -// Every SDK's extractor hands this the same JSON shape, so every page comes out the same way. -// Capabilities come from sdk-specs: an entry's `capability` slug picks its area and heading. -// Entries with no capability go to SDK utils. Usage: -// -// node render.mjs > reference.md -import { readFileSync } from 'node:fs' -import { fileURLToPath } from 'node:url' - -/** The heading id the docs site and GitHub give a heading with this text. */ -export function slugify(text) { - return text - .toLowerCase() - .replace(/[^\p{L}\p{N}\- ]/gu, '') - .replace(/ /g, '-') -} - -const fence = (code, lang) => '```' + lang + '\n' + code.trim() + '\n```' -const code = (text) => (text ? '`' + String(text).replace(/\s*\n\s*/g, ' ').trim() + '`' : '') -/** - * Table cell text. Code fences become inline code, paragraphs join with
, and pipes are - * escaped. A pipe inside a code span splits it into spans around `\|`, since the site's MDX shows - * `\|` inside one span literally. - */ -const cellText = (text) => - String(text ?? '') - .trim() - .replace(/```\w*\n([\s\S]*?)```/g, (_, c) => '`' + c.trim().replace(/\s*\n\s*/g, '; ') + '`') - .split(/\n\s*\n/) - .map((paragraph) => paragraph.replace(/\s*\n\s*/g, ' ')) - .join('
') - .split(/(`[^`]*`)/) - .map((part) => - !part.startsWith('`') - ? part.replace(/\|/g, '\\|') - : part.slice(1, -1).split(/\s*\|\s*/).map((x) => '`' + x + '`').join(' \\| ') - ) - .join('') - -/** - * One details block holding a one-column table: a row per parameter, field or setting with its - * name, a labeled type and default line, then its description. - */ -function members(title, rows) { - if (!rows?.length) return null - const head = { Parameters: 'Parameter', Fields: 'Field', Settings: 'Setting' }[title] - const lines = rows.map((r) => { - const labels = [ - r.where && `**Where:** ${r.where}`, - r.value && `**Value:** ${cellText(code(r.value))}`, - r.type && `**Type:** ${cellText(code(r.type))}`, - r.required ? '**Required**' : r.default && `**Default:** ${cellText(code(r.default))}`, - ].filter(Boolean) - const cell = [code(r.name), labels.join(' · '), r.description && cellText(r.description)].filter(Boolean).join('
') - return `| ${cell} |` - }) - return ['
', `${title} (${rows.length})`, '', `| ${head} |`, '| --- |', ...lines, '', '
'].join('\n') -} - -const sentence = (text) => (text ? text[0].toUpperCase() + text.slice(1) + (/[.!?]$/.test(text) ? '' : '.') : '') - -function example(ex, lang) { - const label = - // Source examples are correct usage unless marked as a counter example. - ex.label === 'incorrect' ? `**Counter example (incorrect).** ${sentence(ex.why)}`.trim() : '**Correct example.**' - return `${label}\n\n${fence(ex.code, lang)}` -} - -function entry(e, level, lang) { - const out = [`${'#'.repeat(level)} \`${e.qualname}\``] - if (e.deprecated) out.push(`**Deprecated.** ${e.deprecated}`) - if (e.availableIn) out.push(`**Available in:** ${e.availableIn}`) - if (e.description) out.push(e.description) - if (e.signature) out.push(fence(e.signature, lang)) - const params = members('Parameters', e.params) - if (params) out.push(params) - if (e.returns && (e.returns.type || e.returns.description)) { - out.push(`**Returns** ${code(e.returns.type)}${e.returns.description ? `. ${e.returns.description}` : ''}`.trim()) - } - for (const r of e.raises ?? []) out.push(`**Raises** ${code(r.type)}. ${r.description ?? ''}`.trim()) - if (e.notes) out.push(e.notes) - for (const ex of e.examples ?? []) out.push(example(ex, lang)) - if (e.source) out.push(`[Source](${e.source})`) - return out.join('\n\n') -} - -/** Moves every heading down one level, dropping the H1 when asked. Code fences stay as written. */ -function nest(markdown, { dropTitle = true } = {}) { - let inFence = false - return markdown - .trim() - .split('\n') - .flatMap((line) => { - if (/^\s*(```|~~~)/.test(line)) inFence = !inFence - if (inFence || !/^#{1,5} /.test(line)) return [line] - return dropTitle && line.startsWith('# ') ? [] : ['#' + line] - }) - .join('\n') - .replace(/^\n+/, '') -} - -function capabilityOf(e, index) { - const cap = index.capabilities[e.capability] - if (!cap) throw new Error(`Unknown capability "${e.capability}" on ${e.qualname}`) - if (slugify(cap.title) !== e.capability) throw new Error(`Title "${cap.title}" doesn't produce slug "${e.capability}"`) - return cap -} - -const order = (index) => (e) => { - const cap = index.capabilities[e.capability] - return [index.areas.indexOf(cap.area), Object.keys(index.capabilities).indexOf(e.capability)] -} - -function byOrder(entries, index) { - const key = order(index) - return [...entries].sort((a, b) => { - const [x, y] = [key(a), key(b)] - return x[0] - y[0] || x[1] - y[1] - }) -} - -function groups(entries, keyOf) { - const map = new Map() - for (const e of entries) map.set(keyOf(e), [...(map.get(keyOf(e)) ?? []), e]) - return map -} - -function defaultReference(ref, index, lang) { - const mapped = ref.entries.filter((e) => e.capability) - mapped.forEach((e) => capabilityOf(e, index)) - const out = [] - // Each sdk-specs capability is a top-level section, in area order. Its heading's anchor is the shared slug. - for (const [slug, entries] of groups(byOrder(mapped, index), (e) => e.capability)) { - out.push(`## ${index.capabilities[slug].title}`, ...entries.map((e) => entry(e, 3, lang))) - } - const utils = ref.entries.filter((e) => !e.capability) - if (utils.length) { - out.push('## SDK utils') - for (const [group, entries] of groups(utils, (e) => e.group ?? 'Other')) { - out.push(`### ${group}`, ...entries.map((e) => entry(e, 4, lang))) - } - } - return out -} - -function secondaryReference(ref, index, lang) { - const mapped = ref.entries.filter((e) => e.capability) - mapped.forEach((e) => capabilityOf(e, index)) - const entries = [...byOrder(mapped, index), ...ref.entries.filter((e) => !e.capability)] - return [`## ${ref.title}`, ...(ref.intro ? [ref.intro] : []), ...entries.map((e) => entry(e, 3, lang))] -} - -function type(t, lang) { - if (!t.description && !t.fields?.length && !t.methods?.length) return [] - const out = [`### \`${t.name}\``] - if (t.description) out.push(t.description) - const fields = members('Fields', t.fields) - if (fields) out.push(fields) - out.push(...(t.methods ?? []).map((m) => entry(m, 4, lang))) - if (t.source) out.push(`[Source](${t.source})`) - return [out.join('\n\n')] -} - -export function render(page, index) { - const lang = page.language - const [first, ...rest] = page.references - // current: the released major customers install. experimental: a newer major that hasn't shipped. - if (!['current', 'experimental'].includes(page.status)) throw new Error(`Unknown status "${page.status}"`) - // Getting started renders before the reference, so its headings would take capability anchors first. - for (const [, title] of page.gettingStarted.matchAll(/^#{2,6} (.+)$/gm)) { - if (index.capabilities[slugify(title)]) throw new Error(`Getting started heading "${title}" takes the "${slugify(title)}" anchor`) - } - const sections = [ - ['---', `title: ${page.title}`, `sdk: ${page.sdk}`, `version: ${page.version}`, `major: ${page.major}`, `status: ${page.status}`, '---'].join('\n'), - '## Getting started', - nest(page.gettingStarted), - ...defaultReference(first, index, lang), - ...rest.flatMap((ref) => secondaryReference(ref, index, lang)), - ] - const configs = members('Settings', page.configs) - if (configs) sections.push('## Configs', configs) - const types = (page.types ?? []).flatMap((t) => type(t, lang)) - if (types.length) sections.push('## Types', ...types) - return sections.join('\n\n') + '\n' -} - -/** The SDK's migration guides as their own page, newest first. Each guide's title becomes an H2. */ -export function renderMigration(page) { - if (!page.migrations?.length) return null - const head = ['---', `title: ${page.title} migration guides`, `sdk: ${page.sdk}`, 'kind: migration', '---'].join('\n') - return [head, ...page.migrations.map((guide) => nest(guide, { dropTitle: false }))].join('\n\n') + '\n' -} - -if (process.argv[1] === fileURLToPath(import.meta.url)) { - const [pagePath, indexPath, flag] = process.argv.slice(2) - const read = (p) => JSON.parse(readFileSync(p, 'utf8')) - process.stdout.write((flag === '--migration' ? renderMigration(read(pagePath)) : render(read(pagePath), read(indexPath))) ?? '') -} diff --git a/bin/docs_scripts/render.test.mjs b/bin/docs_scripts/render.test.mjs deleted file mode 100644 index ad09ae3de..000000000 --- a/bin/docs_scripts/render.test.mjs +++ /dev/null @@ -1,167 +0,0 @@ -import { test } from 'node:test' -import assert from 'node:assert/strict' -import { render, renderMigration, slugify } from './render.mjs' - -const capabilities = { - areas: ['Events', 'Feature flags', 'Lifecycle'], - capabilities: { - capture: { title: 'Capture', area: 'Events' }, - 'get-feature-flag': { title: 'Get feature flag', area: 'Feature flags' }, - flush: { title: 'Flush', area: 'Lifecycle' }, - }, -} - -const capture = { - qualname: 'Client.capture', - capability: 'capture', - signature: 'capture(event: str, **kwargs) -> Optional[str]', - description: 'Captures an event.', - params: [ - { name: 'event', type: 'str', required: true, description: 'The event name.' }, - { name: 'timestamp', type: 'datetime | str', default: 'None', description: 'When it happened | UTC.' }, - { name: 'secret_key', default: 'None', description: 'A secret key.\nExample:\n\n```python\nClient(key, secret_key="phx_")\n```' }, - ], - returns: { type: 'Optional[str]', description: 'The event UUID, or None when it was dropped.' }, - examples: [ - { code: 'client.capture("signed_up", distinct_id="u1")' }, - { label: 'incorrect', why: 'fails because `distinct_id` is keyword-only', code: 'client.capture("u1", "signed_up")' }, - { label: 'correct', code: 'client.capture("signed_up", distinct_id="u1")' }, - ], -} - -const page = { - sdk: 'python', - title: 'PostHog Python SDK', - version: '8.0.0', - major: 8, - status: 'experimental', - language: 'python', - gettingStarted: '# Getting started\n\nInstall it.\n\n## Install\n\n```bash\npip install posthog\n```', - references: [ - { - title: 'Client', - entries: [ - { qualname: 'Client.flush', capability: 'flush', description: 'Sends queued events.' }, - capture, - { qualname: 'Client.tag', capability: null, group: 'Contexts', description: 'Adds a tag.' }, - ], - }, - { - title: 'AsyncClient', - entries: [{ qualname: 'AsyncClient.capture', capability: 'capture', description: 'Captures an event.' }], - }, - ], - configs: [{ name: 'POSTHOG_MW_CAPTURE_EXCEPTIONS', where: 'Django settings', type: 'bool', default: 'True', description: 'Captures exceptions.' }], - types: [ - { - name: 'FeatureFlagEvaluations', - kind: 'class', - description: 'A snapshot of flag values.', - fields: [], - methods: [{ qualname: 'FeatureFlagEvaluations.is_enabled', signature: 'is_enabled(key: str) -> bool', description: 'Checks a flag.' }], - }, - { name: 'Empty', kind: 'class', description: '', fields: [], methods: [] }, - { name: 'Compression', description: 'Request compression.', fields: [{ name: 'GZIP', value: '"gzip"', description: 'gzip, from the standard library.' }], methods: [] }, - ], - migrations: ['# Migrating from 7.x to 8.0\n\n## Capture\n\nIt changed.', '# Migrating from 6.x to 7.0\n\nOlder.'], -} - -const md = render(page, capabilities) -const h2 = md.split('\n').filter((l) => l.startsWith('## ')).map((l) => l.slice(3)) - -test('slugify matches heading ids', () => { - assert.equal(slugify('Get feature flag'), 'get-feature-flag') - assert.equal(slugify('Get anonymous ID'), 'get-anonymous-id') - assert.equal(slugify('`Client.capture`'), 'clientcapture') -}) - -test('top-level headings are capabilities in area order, then structure', () => { - assert.deepEqual(h2, ['Getting started', 'Capture', 'Flush', 'SDK utils', 'AsyncClient', 'Configs', 'Types']) -}) - -test('capability headings carry the shared slug', () => { - assert.match(md, /^## Capture$/m) - assert.equal(slugify('Capture'), 'capture') - assert.match(md, /^## Flush$/m) - assert.doesNotMatch(md, /^## Events$/m) -}) - -test('unknown capabilities and slug mismatches throw', () => { - const bad = { ...page, references: [{ title: 'Client', entries: [{ qualname: 'x', capability: 'nope' }] }] } - assert.throws(() => render(bad, capabilities), /Unknown capability "nope"/) - const mismatch = { ...capabilities, capabilities: { ...capabilities.capabilities, capture: { title: 'Capture events', area: 'Events' } } } - assert.throws(() => render(page, mismatch), /slug/) -}) - -test('a getting started heading that would take a capability slug throws', () => { - const clash = { ...page, gettingStarted: '# Getting started\n\n## Capture\n\nSend one.' } - assert.throws(() => render(clash, capabilities), /Getting started heading "Capture" takes the "capture" anchor/) -}) - -test('an entry renders signature, parameters, returns and labeled examples', () => { - assert.match(md, /^### `Client\.capture`$/m) - assert.match(md, /```python\ncapture\(event: str, \*\*kwargs\) -> Optional\[str\]\n```/) - assert.match(md, /
\nParameters \(3\)<\/summary>\n\n\| Parameter \|\n\| --- \|\n\| `event`
\*\*Type:\*\* `str` · \*\*Required\*\*
The event name\. \|\n/) - assert.match(md, /^\| `timestamp`
\*\*Type:\*\* `datetime` \\\| `str` · \*\*Default:\*\* `None`
When it happened \\\| UTC\. \|$/m) - assert.match(md, /^\| `secret_key`
\*\*Default:\*\* `None`
A secret key\. Example:
`Client\(key, secret_key="phx_"\)` \|\n\n<\/details>/m) - assert.doesNotMatch(md, /^#####/m) - assert.match(md, /\*\*Returns\*\* `Optional\[str\]`\. The event UUID/) - assert.match(md, /\*\*Correct example\.\*\*\n\n```python\nclient\.capture\("signed_up", distinct_id="u1"\)\n```\n\n\*\*Counter/) - assert.doesNotMatch(md, /\*\*Example\.\*\*/) - assert.match(md, /\*\*Counter example \(incorrect\)\.\*\* Fails because `distinct_id` is keyword-only\.\n\n```python/) - assert.match(md, /\*\*Correct example\.\*\*\n\n```python\nclient\.capture\("signed_up", distinct_id="u1"\)/) -}) - -test('member names sit in table rows, so they never take a capability anchor', () => { - const clash = { ...page, references: [{ title: 'Client', entries: [{ qualname: 'Client(...)', capability: 'capture', params: [{ name: 'flush', default: 'None' }] }, { qualname: 'Client.flush', capability: 'flush' }] }] } - const out = render(clash, capabilities) - assert.match(out, /^\| `flush`
\*\*Default:\*\* `None` \|$/m) - assert.doesNotMatch(out, /^#+ `flush`/m) -}) - -test('every parameter block sits in a details block, even a short one', () => { - const one = { ...page, references: [{ title: 'Client', entries: [{ qualname: 'Client.flush', capability: 'flush', params: [{ name: 'timeout', default: '5' }] }] }] } - assert.match(render(one, capabilities), /
\nParameters \(1\)<\/summary>\n\n\| Parameter \|\n\| --- \|\n\| `timeout`
\*\*Default:\*\* `5` \|\n\n<\/details>/) - assert.equal((md.match(/
/g) ?? []).length, (md.match(/<\/details>/g) ?? []).length) - assert.doesNotMatch(md, /^- `/m) -}) - -test('unmapped entries land in SDK utils under their group', () => { - const utils = md.slice(md.indexOf('## SDK utils')) - assert.match(utils, /^### Contexts\n\n#### `Client\.tag`/m) -}) - -test('secondary references list entries without area headings', () => { - const asyncRef = md.slice(md.indexOf('## AsyncClient'), md.indexOf('## Configs')) - assert.match(asyncRef, /^### `AsyncClient\.capture`$/m) - assert.doesNotMatch(asyncRef, /^### Capture$/m) -}) - -test('types explain members and skip empty stubs', () => { - assert.match(md, /^### `FeatureFlagEvaluations`\n\nA snapshot of flag values\./m) - assert.match(md, /^#### `FeatureFlagEvaluations\.is_enabled`$/m) - assert.doesNotMatch(md, /`Empty`/) - assert.match(md, /^\| `GZIP`
\*\*Value:\*\* `"gzip"`
gzip, from the standard library\. \|$/m) -}) - -test('guides drop their H1 and nest under their section', () => { - assert.match(md, /## Getting started\n\nInstall it\.\n\n### Install/) - assert.doesNotMatch(md, /^# /m) -}) - -test('configs render as headed settings and the page has no reverse usage lists', () => { - assert.match(md, /^## Configs\n\n
\nSettings \(1\)<\/summary>\n\n\| Setting \|\n\| --- \|\n\| `POSTHOG_MW_CAPTURE_EXCEPTIONS`
\*\*Where:\*\* Django settings · \*\*Type:\*\* `bool` · \*\*Default:\*\* `True`
Captures exceptions\. \|$/m) - assert.doesNotMatch(md, /Used by/i) -}) - -test('front matter names the SDK and version', () => { - assert.match(md, /^---\ntitle: PostHog Python SDK\nsdk: python\nversion: 8\.0\.0\nmajor: 8\nstatus: experimental\n---\n/) - assert.throws(() => render({ ...page, status: 'preview' }, capabilities), /status/) -}) - -test('migration guides render as their own page, each guide an H2', () => { - const out = renderMigration(page) - assert.match(out, /^---\ntitle: PostHog Python SDK migration guides\nsdk: python\nkind: migration\n---\n\n## Migrating from 7\.x to 8\.0\n\n### Capture\n\nIt changed\.\n\n## Migrating from 6\.x to 7\.0\n\nOlder\.\n$/) - assert.doesNotMatch(md, /Migrating from/) - assert.equal(renderMigration({ ...page, migrations: [] }), null) -}) diff --git a/bin/docs_scripts/sdk_specs.py b/bin/docs_scripts/sdk_specs.py deleted file mode 100644 index 6a4710636..000000000 --- a/bin/docs_scripts/sdk_specs.py +++ /dev/null @@ -1,43 +0,0 @@ -"""Read sdk-specs capabilities for the reference: each slug's title, in the README table's order. - -The slug is the folder name under `openspec/specs/`, and it becomes the capability's anchor on -the reference page. Pass a checkout of PostHog/sdk-specs at the commit to document against. -""" - -import re -import subprocess -from pathlib import Path - -ACRONYMS = {"id": "ID", "ai": "AI", "http": "HTTP", "mcp": "MCP"} - - -def sentence_case(name: str) -> str: - words = [ - ACRONYMS.get(w.lower(), w if w.isupper() and len(w) > 1 else w.lower()) - for w in name.split() - ] - words[0] = words[0][0].upper() + words[0][1:] - return " ".join(words) - - -def capabilities(checkout: str) -> dict: - """The renderer's capability index. It fails when a spec folder has no README row.""" - root = Path(checkout) - readme = (root / "README.md").read_text() - rows = re.findall( - r"^\| ([^|]+?) \| \[`openspec/specs/([a-z0-9-]+)/spec\.md`\]", readme, re.M - ) - if not rows: - raise SystemExit(f"{root / 'README.md'} has no capability table") - caps = {slug: {"title": sentence_case(name)} for name, slug in rows} - folders = {p.name for p in (root / "openspec/specs").iterdir() if p.is_dir()} - if folders - set(caps): - raise SystemExit( - f"sdk-specs capabilities missing from its README table: {sorted(folders - set(caps))}" - ) - head = subprocess.run( - ["git", "-C", str(root), "rev-parse", "--short", "HEAD"], - capture_output=True, - text=True, - ) - return {"sdkSpecs": head.stdout.strip(), "areas": [], "capabilities": caps} From 7c20b46a569092677e5c779296f4dc606375dfb0 Mon Sep 17 00:00:00 2001 From: "Vincent (Wen Yu) Ge" Date: Wed, 4 Nov 2026 17:00:41 -0500 Subject: [PATCH 3/8] docs: write the latest reference as its own file, with the site's front matter Co-Authored-By: Claude Opus 5.5 --- bin/docs_scripts/generate_markdown.py | 44 ++++++++++++++++++++------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/bin/docs_scripts/generate_markdown.py b/bin/docs_scripts/generate_markdown.py index 1a4bb41e1..5503b55b4 100644 --- a/bin/docs_scripts/generate_markdown.py +++ b/bin/docs_scripts/generate_markdown.py @@ -18,10 +18,7 @@ ROOT = Path(__file__).resolve().parents[2] SOURCE = "[Source](https://github.com/PostHog/posthog-python/blob/{ref}/{path}#L{line})" -PAGE = "---\ntitle: PostHog Python SDK\nsdk: python\nversion: {}\nmajor: {}\nstatus: {}\n---" -GUIDES = ( - "---\ntitle: PostHog Python SDK migration guides\nsdk: python\nkind: migration\n---" -) +GUIDES = "---\ntitle: PostHog Python SDK migration guides\nsidebar: Docs\nshowTitle: true\n---" TYPES = """args.OptionalCaptureArgs args.OptionalSetArgs feature_flag_evaluations.FeatureFlagEvaluations types.FeatureFlagResult types.FeatureFlag types.FlagsAndPayloads types.FeatureFlagEvaluationRuntime types.UnresolvedFlagReason flag_definition_cache.FlagDefinitionCacheProvider flag_definition_cache.FlagDefinitionCacheData @@ -478,6 +475,27 @@ def nest(markdown: str, drop_title: bool = True) -> str: return "\n".join(out).lstrip("\n") +def front_matter(title: str, sections: list[str], noindex: bool = False) -> str: + """The docs site's front matter. Its ToC lists the page's H2 sections only.""" + toc = [] + for heading in [s[3:] for s in sections if s.startswith("## ")]: + anchor = re.sub(r"[^\w\- ]", "", heading.lower()).replace(" ", "-") + toc.append(f' - value: "{heading}"\n url: {anchor}\n depth: 0') + head = [ + f'title: "{title}"', + "sidebar: Docs", + "showTitle: true", + "contentMaxWidthClass: max-w-full", + ] + head += [ + "platformLogo: python", + *["noindex: true"] * noindex, + "tableOfContents:", + *toc, + ] + return "\n".join(["---", *head, "---"]) + + def write(name: str, sections: list[str]) -> None: (ROOT / "references" / name).write_text("\n\n".join(sections) + "\n") print(f"wrote references/{name}") @@ -503,15 +521,19 @@ def main() -> None: ref = opts.source_ref or f"v{version}" started = nest((ROOT / "docs/getting-started.md").read_text()) reference_md = reference(pkg, capabilities(opts.sdk_specs), ref) + body = ["## Getting started", started, *reference_md] + # Each major gets its own page. The current one is also the latest reference. + title = f"PostHog Python SDK {major}.x" + ( + "" if status == "current" else " (experimental)" + ) write( - f"posthog-python-{major}.md", - [ - PAGE.format(version, major, status), - "## Getting started", - started, - *reference_md, - ], + f"posthog-python-{major}.md", [front_matter(title, body, noindex=True), *body] ) + if status == "current": + write( + "posthog-python-reference.md", + [front_matter("PostHog Python SDK", body), *body], + ) # Newest guide first, so the migration page opens on the latest upgrade. if guides := sorted((ROOT / "docs").glob("migration-*.md"), reverse=True): write( From bdfa2181de4ac312a714d010fc932b5575962549 Mon Sep 17 00:00:00 2001 From: "Vincent (Wen Yu) Ge" Date: Wed, 4 Nov 2026 17:16:38 -0500 Subject: [PATCH 4/8] docs: leave indexing to the site Co-Authored-By: Claude Opus 5.5 --- bin/docs_scripts/generate_markdown.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/bin/docs_scripts/generate_markdown.py b/bin/docs_scripts/generate_markdown.py index 5503b55b4..fbaf81eae 100644 --- a/bin/docs_scripts/generate_markdown.py +++ b/bin/docs_scripts/generate_markdown.py @@ -475,21 +475,16 @@ def nest(markdown: str, drop_title: bool = True) -> str: return "\n".join(out).lstrip("\n") -def front_matter(title: str, sections: list[str], noindex: bool = False) -> str: +def front_matter(title: str, sections: list[str]) -> str: """The docs site's front matter. Its ToC lists the page's H2 sections only.""" toc = [] for heading in [s[3:] for s in sections if s.startswith("## ")]: anchor = re.sub(r"[^\w\- ]", "", heading.lower()).replace(" ", "-") toc.append(f' - value: "{heading}"\n url: {anchor}\n depth: 0') - head = [ - f'title: "{title}"', - "sidebar: Docs", - "showTitle: true", - "contentMaxWidthClass: max-w-full", - ] + head = [f'title: "{title}"', "sidebar: Docs", "showTitle: true"] head += [ + "contentMaxWidthClass: max-w-full", "platformLogo: python", - *["noindex: true"] * noindex, "tableOfContents:", *toc, ] @@ -526,9 +521,7 @@ def main() -> None: title = f"PostHog Python SDK {major}.x" + ( "" if status == "current" else " (experimental)" ) - write( - f"posthog-python-{major}.md", [front_matter(title, body, noindex=True), *body] - ) + write(f"posthog-python-{major}.md", [front_matter(title, body), *body]) if status == "current": write( "posthog-python-reference.md", From 1223cb53d5ff9a96585b2a173f60fcf8aac45887 Mon Sep 17 00:00:00 2001 From: "Vincent (Wen Yu) Ge" Date: Wed, 4 Nov 2026 17:43:27 -0500 Subject: [PATCH 5/8] docs: render the reference with griffe2md Co-Authored-By: Claude Opus 5.5 --- bin/docs_scripts/generate_markdown.py | 554 +++++--------------------- pyproject.toml | 1 + uv.lock | 56 +++ 3 files changed, 163 insertions(+), 448 deletions(-) diff --git a/bin/docs_scripts/generate_markdown.py b/bin/docs_scripts/generate_markdown.py index fbaf81eae..ae0474c21 100644 --- a/bin/docs_scripts/generate_markdown.py +++ b/bin/docs_scripts/generate_markdown.py @@ -1,478 +1,122 @@ -"""Generate the SDK reference Markdown from source with Griffe, which never imports the SDK. +"""Generate the SDK reference Markdown from source with griffe2md, which never imports the SDK. A `posthog-capability: ` docstring line puts a symbol under that PostHog/sdk-specs -capability, in the sdk-specs README's order. Untagged symbols go to SDK utils, grouped by -their `Category:`. +capability, in the sdk-specs README's order. Untagged symbols go to SDK utils. - python bin/docs_scripts/generate_markdown.py --sdk-specs DIR [--major 8 --source-ref v1] + python bin/docs_scripts/generate_markdown.py --sdk-specs DIR [--major 8] """ import argparse import logging import re -import textwrap from pathlib import Path -from typing import NamedTuple import griffe +import griffe2md ROOT = Path(__file__).resolve().parents[2] -SOURCE = "[Source](https://github.com/PostHog/posthog-python/blob/{ref}/{path}#L{line})" -GUIDES = "---\ntitle: PostHog Python SDK migration guides\nsidebar: Docs\nshowTitle: true\n---" -TYPES = """args.OptionalCaptureArgs args.OptionalSetArgs feature_flag_evaluations.FeatureFlagEvaluations -types.FeatureFlagResult types.FeatureFlag types.FlagsAndPayloads types.FeatureFlagEvaluationRuntime -types.UnresolvedFlagReason flag_definition_cache.FlagDefinitionCacheProvider flag_definition_cache.FlagDefinitionCacheData -feature_flags.InconclusiveMatchError feature_flags.RequiresServerEvaluation capture_send.CaptureError -capture_compression.CaptureCompression tracing.span.Span -metrics_capture.PostHogMetrics emails.Emails emails.AsyncEmails emails.EmailError emails.EmailStatus -emails.SendEmailResult ai.prompts.Prompts""".split() -UTILS = { - "Framework integrations": """integrations.django.PosthogContextMiddleware integrations.flask.PosthogFlaskIntegration - integrations.asgi.PosthogASGIMiddleware integrations.celery.PosthogCeleryIntegration - integrations.drf.create_exception_handler integrations.otel.PostHogContextSpanProcessor - integrations.otel.enrich_current_span""".split(), - "HTTP connection": "request.set_socket_options request.enable_keep_alive request.disable_connection_reuse".split(), -} -WRAPPERS = "openai anthropic gemini langchain openai_agents claude_agent_sdk".split() -CONFIGS = [("integrations.django.PosthogContextMiddleware", "Django settings")] -# Where a symbol works, by module, when that isn't everywhere Python runs. -AVAILABLE_IN = { - "async_client": "the `async` extra, `posthog[async]`", - "integrations.django": "Django apps", - "integrations.drf": "Django REST framework apps", - "integrations.flask": "Flask apps", - "integrations.celery": "Celery workers", - "integrations.asgi": "ASGI apps, such as FastAPI and Starlette", - "integrations.otel": "the `otel` extra, `posthog[otel]`", - "ai.langchain": "the `langchain` extra, `posthog[langchain]`", - "ai.openai": "apps with the `openai` package", - "ai.anthropic": "apps with the `anthropic` package", - "ai.gemini": "apps with the Google Gen AI package", - "ai.openai_agents": "apps with the `openai-agents` package", - "ai.claude_agent_sdk": "apps with the `claude-agent-sdk` package", -} -ACRONYMS = {"id": "ID", "ai": "AI", "http": "HTTP", "mcp": "MCP"} -STAR = {"variadic positional": "*", "variadic keyword": "**"} +TYPES = """args.OptionalCaptureArgs feature_flag_evaluations.FeatureFlagEvaluations types.FeatureFlagResult +types.FeatureFlag types.FlagsAndPayloads types.UnresolvedFlagReason feature_flags.InconclusiveMatchError +feature_flags.RequiresServerEvaluation capture_send.CaptureError tracing.span.Span emails.Emails +emails.EmailError emails.SendEmailResult""".split() +UTILS = """integrations.django.PosthogContextMiddleware integrations.flask.PosthogFlaskIntegration +integrations.asgi.PosthogASGIMiddleware integrations.celery.PosthogCeleryIntegration +request.set_socket_options request.enable_keep_alive request.disable_connection_reuse""".split() TAG = re.compile(r"^[ \t]*posthog-capability:[ \t]*([a-z0-9-]+)[ \t]*$\n?", re.M) -CAPABILITY_ROW = re.compile( - r"^\| ([^|]+?) \| \[`openspec/specs/([a-z0-9-]+)/spec\.md`\]", re.M -) -FENCE = re.compile(r"```(\w*)\n(.*?)```", re.S) -LABEL = re.compile(r"#\s*(Correct example|Counter example \(incorrect\))\.?:?\s*(.*)") ROLE = re.compile( r":(?:py:)?(?:meth|func|class|attr|obj|data|exc|mod|const):`~?([^`]+)`" ) -LITERAL = re.compile(r"^([^\n]*?)::\n\n((?:(?:[ \t]+[^\n]*)?\n)+)", re.M) -LITERAL_VALUE = re.compile( - r"""^(None|True|False|-?\d[\d_.e]*|(["']).*\2|\[\]|\{\}|\(\))$""" -) -SETTING = re.compile(r"^\s*- `(POSTHOG_[A-Z_]+)`: (.*?)(?=^\s*- `|^\s*$)", re.M | re.S) -VALUE = re.compile(r"^(\w+): (.*?)(?=^\w+:|\Z)", re.M | re.S) -NEWLINE = re.compile(r"\s*\n\s*") - - -class Entry(NamedTuple): - slug: str | None - group: str | None - name: str - body: list[str] - - def render(self, level: int) -> str: - return "\n\n".join([f"{'#' * level} `{self.name}`", *self.body]) - - -def text(value) -> str: - return "" if value is None else str(value) - - -def get(obj, path: str): - try: - return obj[path] - except KeyError: - return None - - -def fence(code: str) -> str: - return f"```python\n{code.strip()}\n```" - - -def code(value) -> str: - return f"`{NEWLINE.sub(' ', str(value)).strip()}`" if value else "" - - -def md(value: str) -> str: - """Sphinx roles, double backticks and literal blocks become Markdown.""" - value = re.sub(r"``([^`]+)``", r"`\1`", ROLE.sub(r"`\1`", value or "")) - - def block(m: re.Match) -> str: - lead = m.group(1) + ":\n\n" if m.group(1).strip() else "" - return lead + fence(textwrap.dedent(m.group(2))) + "\n\n" - - return LITERAL.sub(block, value + "\n").strip() - - -def unpipe(span: str) -> str: - """Escapes pipes for a table. A pipe splits its code span, since MDX shows `\\|` literally.""" - if not span.startswith("`"): - return span.replace("|", "\\|") - return " \\| ".join(f"`{x}`" for x in re.split(r"\s*\|\s*", span[1:-1])) - - -def cell(value) -> str: - """Table cell text: fences become inline code and paragraphs join with
.""" - value = FENCE.sub( - lambda m: f"`{NEWLINE.sub('; ', m.group(2).strip())}`", text(value).strip() - ) - value = "
".join(NEWLINE.sub(" ", p) for p in re.split(r"\n\s*\n", value)) - return "".join(unpipe(s) for s in re.split(r"(`[^`]*`)", value)) - - -def row(name, desc="", kind="", default="", required=False, value="", where="") -> str: - """One parameter, field or setting: its name, a labels line, then its description.""" - default = ( - "**Required**" - if required - else default and f"**Default:** {cell(code(default))}" - ) - labels = [ - where and f"**Where:** {where}", - value and f"**Value:** {cell(code(value))}", - kind and f"**Type:** {cell(code(kind))}", - default, - ] - parts = [code(name), " · ".join(x for x in labels if x), cell(desc)] - return "| " + "
".join(x for x in parts if x) + " |" - - -def table(title: str, rows: list[str]) -> list[str]: - """A details block holding a one-column table.""" - head = f"{title} ({len(rows)})\n\n| {title[:-1]} |\n| --- |" - return ["\n".join(["
", head, *rows, "", "
"])] if rows else [] - - -def parse(obj) -> tuple[str | None, list]: - """The symbol's capability tag and its parsed docstring sections.""" - raw = obj.docstring.value if obj.docstring else "" - tag, body = TAG.search(raw), TAG.sub("", raw).strip() - sections = griffe.Docstring(body, parent=obj).parse("google") if body else [] - return (tag.group(1) if tag else None), sections - - -def examples(blocks: str) -> list[str]: - """Each fenced block, labeled by its first comment line. Unlabeled blocks are correct.""" - out = [] - for _, src in FENCE.findall(blocks): - head, _, rest = src.lstrip("\n").partition("\n") - label = "**Correct example.**" - if m := LABEL.match(head.strip()): - src, why = rest, m.group(2) - if m.group(1).startswith("Counter"): - why = ( - why[:1].upper() - + why[1:] - + ("." if why and why[-1] not in ".!?" else "") - ) - label = f"**Counter example (incorrect).** {why}".strip() - out.append(f"{label}\n\n{fence(src)}") - return out - - -def public(p) -> bool: - return p.name not in ("self", "cls") and not p.name.startswith("_") - - -def signature(fn, name: str) -> str: - parts = [] - for p in filter(public, fn.parameters): - star = STAR.get(p.kind.value, "") - part = star + p.name + (f": {p.annotation}" if p.annotation is not None else "") - parts.append( - part + (f" = {p.default}" if p.default is not None and not star else "") - ) - returns = ( - f" -> {fn.returns}" if fn.returns is not None and name[0].islower() else "" - ) - head = ("async " if "async" in fn.labels else "") + name - if len(line := f"{head}({', '.join(parts)}){returns}") <= 88: - return line - return f"{head}(\n" + "".join(f" {p},\n" for p in parts) + f"){returns}" - - -def params(fn, documented: dict) -> list[str]: - """A row per parameter. Each documented key of an `Unpack[...]` TypedDict gets a row.""" - rows = [] - for p in filter(public, fn.parameters): - star = STAR.get(p.kind.value, "") - unpacked = re.fullmatch(r"Unpack\[(\w+)\]", text(p.annotation)) - typed = get(fn.package, f"args.{unpacked.group(1)}") if unpacked else None - if star == "**" and typed and (fields := typed.attributes): - for n in [n for n in documented if n in fields]: - rows.append(row(n, md(documented.pop(n)), text(fields[n].annotation))) - continue - desc = md(documented.pop(p.name, "")) - default, required = ( - "" if star else text(p.default), - p.default is None and not star, - ) - rows.append(row(star + p.name, desc, text(p.annotation), default, required)) - return rows + [row(n, md(d)) for n, d in documented.items()] +CONFIG = { + **griffe2md.default_config, + "docstring_section_style": "table", + "show_root_full_path": False, + "show_signature_annotations": True, + "separate_signature": True, + "heading_level": 3, +} -def entry(obj, name: str, ref: str, group: str | None = None) -> Entry: - slug, sections = parse(obj) - texts, notes, shown, raises = [], [], [], [] - documented: dict[str, str] = {} - returns, deprecated = ("", ""), "" - for s in sections: - kind, value = s.kind.value, s.value - if kind == "text": - texts.append(value) - elif kind in ("parameters", "other parameters"): - documented |= {p.name: p.description for p in value} - elif kind == "returns": - rtype = text(value[0].annotation) or text(getattr(obj, "returns", "")) - returns = (rtype, md(" ".join(r.description for r in value))) - elif kind == "raises": - raises = [ - f"**Raises** {code(text(r.annotation))}. {md(r.description)}".strip() - for r in value - ] - elif kind == "examples": - shown += examples("\n".join(t for _, t in value)) - elif kind == "deprecated": - deprecated = f"**Deprecated.** {value.description}" - elif kind == "admonition" and s.title in ("Example", "Examples"): - body = value.description - shown += ( - examples(body) - if "```" in body - else [f"**Correct example.**\n\n{fence(body)}"] - ) - elif kind == "admonition" and s.title == "Category": - group = group or value.description.strip() - elif kind == "admonition": - notes.append(f"**{s.title}.** {md(value.description)}") - sig, rows = "", [] - if obj.is_function: - sig = signature( - obj, name.removesuffix("(...)") if obj.name == "__init__" else obj.name - ) - rows = params(obj, documented) - if not any(returns) and obj.returns is not None and obj.name != "__init__": - returns = (text(obj.returns), "") - elif obj.is_class and "__init__" in obj.members: - init = obj.members["__init__"] - args = { - p.name: p.description - for s in parse(init)[1] - if s.kind.value == "parameters" - for p in s.value - } - sig, rows = signature(init, obj.name), params(init, documented | args) - elif obj.is_attribute and obj.annotation is not None: - sig = f"{name}: {obj.annotation}" - where = available_in(obj.module.path) - returned = any(returns) and f"**Returns** {code(returns[0])}" + ( - returns[1] and f". {returns[1]}" - ) - body = [ - deprecated, - where and f"**Available in:** {where}", - md("\n\n".join(texts)), - sig and fence(sig), - *table("Parameters", rows), - returned and returned.strip(), - *raises, - "\n\n".join(notes), - *shown, - SOURCE.format(ref=ref, path=obj.relative_package_filepath, line=obj.lineno), - ] - return Entry(slug, group, name, [b for b in body if b]) +def tag(obj) -> str | None: + """The capability tag. It and any Sphinx roles are stripped from the docstrings rendered.""" + found = TAG.search(obj.docstring.value) if obj.docstring else None + for o in [obj, *(m for m in obj.members.values() if not m.is_alias)]: + if o.docstring: + text = ROLE.sub(r"`\1`", TAG.sub("", o.docstring.value)) + o.docstring.value = re.sub(r"(? str | None: - module = module.removeprefix("posthog.") + "." - return next( - (w for m, w in AVAILABLE_IN.items() if module.startswith(m + ".")), None - ) +def entry(obj, name: str) -> str: + md = griffe2md.render_object_docs(obj, CONFIG) + # Annotation links point at anchors this page doesn't have. + md = re.sub(r"\[([^\]]+)\]\(#[\w.]+\)", r"\1", md) + # A parameter documented only in the docstring, like a **kwargs key, isn't required. + md = re.sub(r"^(`\w+` \| \| .*) \| \*required\*$", r"\1 | ", md, flags=re.M) + return re.sub(r"^### .*$", f"### `{name}`", md.strip(), count=1, flags=re.M) -def members(cls) -> list: +def methods(cls) -> list: + """Public methods and properties, and `__init__`.""" return [ m for n, m in cls.members.items() if not m.is_alias - and (not n.startswith("_") or n == "__init__") and (m.is_function or "property" in m.labels) + and (not n.startswith("_") or n == "__init__") ] -def type_block(cls, ref: str) -> str: - """A type's section. Dataclasses, TypedDicts, enums and errors show their fields.""" - sections = parse(cls)[1] + ( - parse(cls.members["__init__"])[1] if "__init__" in cls.members else [] +def label(prefix: str, m) -> str: + return f"{prefix}(...)" if m.name == "__init__" else f"{prefix}.{m.name}" + + +def capabilities(checkout: str) -> dict[str, str]: + """Each sdk-specs capability's slug and title in sentence case, in the README's order.""" + readme = (Path(checkout) / "README.md").read_text() + rows = re.findall( + r"^\| ([^|]+?) \| \[`openspec/specs/([a-z0-9-]+)/spec\.md`\]", readme, re.M ) - documented = { - i.name: i.description - for s in sections - if s.kind.value in ("attributes", "parameters") - for i in s.value + acronyms = {"id": "ID", "ai": "AI", "http": "HTTP", "mcp": "MCP"} + titles = { + s: " ".join(acronyms.get(w.lower(), w.lower()) for w in n.split()) + for n, s in rows } - for s in sections: - if s.kind.value == "admonition" and s.title == "Values": - documented |= { - n: " ".join(d.split()) for n, d in VALUE.findall(s.value.description) - } - bases = " ".join(text(b) for b in cls.bases) - data = any("dataclass" in text(d.value) for d in cls.decorators) or any( - k in bases for k in ("TypedDict", "Enum", "Exception", "Error") - ) - rows = [] - for n, a in cls.attributes.items(): - if data and not n.startswith("_") and "property" not in a.labels: - # Only a literal is a default. A field set from an __init__ argument has none. - literal = text(a.value) if LITERAL_VALUE.match(text(a.value)) else "" - desc = md(a.docstring.value if a.docstring else documented.get(n, "")) - enum = "Enum" in bases - rows.append( - row(n, desc, value=literal) - if enum - else row(n, desc, text(a.annotation), literal) - ) - methods = [ - entry(m, f"{cls.name}.{m.name}", ref).render(4) - for m in members(cls) - if m.name != "__init__" - ] - desc = md("\n\n".join(s.value for s in parse(cls)[1] if s.kind.value == "text")) - if not (desc or rows or methods): - return "" - src = SOURCE.format(ref=ref, path=cls.relative_package_filepath, line=cls.lineno) - return "\n\n".join( - [ - f"### `{cls.name}`", - *filter(None, [desc]), - *table("Fields", rows), - *methods, - src, - ] - ) + return {s: t[0].upper() + t[1:] for s, t in titles.items()} -def reference(pkg, caps: dict[str, str], ref: str) -> list[str]: - """Every capability, SDK utils, AsyncClient, module functions, configs and types.""" +def has(pkg, path: str) -> bool: + try: + pkg[path] + return True + except KeyError: + return False - def methods(cls: str, prefix: str) -> list[Entry]: - names = {"__init__": "(...)"} - return [ - entry(m, prefix + names.get(m.name, f".{m.name}"), ref) - for m in members(pkg[cls]) - ] - client, asyncs = ( - methods("client.Client", "Client"), - methods("async_client.AsyncClient", "AsyncClient"), +def reference(pkg, caps: dict[str, str]) -> list[str]: + order = {slug: i for i, slug in enumerate(caps)} + utils, types = ([pkg[p] for p in paths if has(pkg, p)] for paths in (UTILS, TYPES)) + client = [(tag(m), m) for m in methods(pkg["client.Client"])] + asyncs = sorted( + ((tag(m), m) for m in methods(pkg["async_client.AsyncClient"])), + key=lambda e: order.get(e[0] or "", len(order)), ) module = [ - entry(m, f"posthog.{n}", ref) + m for n, m in pkg.members.items() if not m.is_alias and m.is_function and not n.startswith("_") ] - for group, paths in UTILS.items(): - for obj, path in [(get(pkg, p), p) for p in paths]: - if obj is not None: - client.append(entry(obj, f"posthog.{path}", ref, group)) - if obj.is_class: - client += [ - entry(m, f"{obj.name}.{m.name}", ref, group) - for m in members(obj) - if m.name != "__init__" - ] - for name in WRAPPERS: - if (mod := get(pkg, f"ai.{name}")) is not None: - names = ", ".join( - f"`{n}`" - for n in mod.members - if not n.startswith("_") and n[0].isupper() - ) - summary = md(mod.docstring.value.split("\n\n")[0]) if mod.docstring else "" - where = available_in(f"ai.{name}") - body = [ - where and f"**Available in:** {where}", - " ".join(filter(None, [summary, f"Exports {names}."])), - ] - client.append( - Entry( - None, - "AI wrapper clients", - f"posthog.ai.{name}", - [b for b in body if b], - ) - ) - if unknown := {e.slug for e in client + asyncs + module if e.slug} - caps.keys(): - raise SystemExit( - f"posthog-capability tags sdk-specs doesn't define: {sorted(unknown)}" - ) - - # Each capability is titled as in sdk-specs, so its heading's anchor is the capability's slug. + for obj in [*module, *utils, *types]: + tag(obj) out = [] for slug, title in caps.items(): - if tagged := [e for e in client if e.slug == slug]: - out += [f"## {title}", *(e.render(3) for e in tagged)] - if utils := [e for e in client if not e.slug]: - out.append("## SDK utils") - for group in dict.fromkeys(e.group or "Other" for e in utils): - out += [ - f"### {group}", - *(e.render(4) for e in utils if (e.group or "Other") == group), - ] - for title, entries in (("AsyncClient", asyncs), ("Module functions", module)): - ordered = sorted( - entries, key=lambda e: list(caps).index(e.slug) if e.slug else len(caps) - ) - out += [f"## {title}", *(e.render(3) for e in ordered)] - settings = [] - for path, where in CONFIGS: - cls = get(pkg, path) - doc = cls.docstring.value if cls and cls.docstring else "" - settings += [ - row(n, md(" ".join(d.split())), where=where) - for n, d in SETTING.findall(doc) - ] - if settings: - out += ["## Configs", *table("Settings", settings)] - if types := [ - t for t in (type_block(pkg[p], ref) for p in TYPES if get(pkg, p)) if t - ]: - out += ["## Types", *types] - return out - - -def capabilities(checkout: str) -> dict[str, str]: - """Each sdk-specs capability's slug and title in sentence case, in the README's order.""" - caps = {} - for name, slug in CAPABILITY_ROW.findall( - (Path(checkout) / "README.md").read_text() - ): - words = [ - ACRONYMS.get(w.lower(), w if w.isupper() and len(w) > 1 else w.lower()) - for w in name.split() - ] - title = " ".join(words) - caps[slug] = title[0].upper() + title[1:] - return caps - - -def nest(markdown: str, drop_title: bool = True) -> str: - """Moves each heading down one level, dropping the H1 when asked. Code stays as written.""" - out, in_fence = [], False - for line in markdown.strip().split("\n"): - in_fence ^= bool(re.match(r"\s*(```|~~~)", line)) - if in_fence or not re.match(r"#{1,5} ", line): - out.append(line) - elif not (drop_title and line.startswith("# ")): - out.append("#" + line) - return "\n".join(out).lstrip("\n") + if tagged := [m for t, m in client if t == slug]: + out += [f"## {title}", *(entry(m, label("Client", m)) for m in tagged)] + out += ["## SDK utils", *(entry(m, label("Client", m)) for t, m in client if not t)] + out += [entry(o, f"posthog.{o.path.removeprefix('posthog.')}") for o in utils] + out += ["## AsyncClient", "Needs the `async` extra, `posthog[async]`."] + out += [entry(m, label("AsyncClient", m)) for _, m in asyncs] + out += ["## Module functions", *(entry(m, f"posthog.{m.name}") for m in module)] + return out + ["## Types", *(entry(o, o.name) for o in types)] def front_matter(title: str, sections: list[str]) -> str: @@ -491,6 +135,18 @@ def front_matter(title: str, sections: list[str]) -> str: return "\n".join(["---", *head, "---"]) +def nest(markdown: str) -> str: + """Moves each heading down one level and drops the H1. Code stays as written.""" + out, in_fence = [], False + for line in markdown.strip().split("\n"): + in_fence ^= bool(re.match(r"\s*```", line)) + if in_fence or not re.match(r"#{1,5} ", line): + out.append(line) + elif not line.startswith("# "): + out.append("#" + line) + return "\n".join(out) + + def write(name: str, sections: list[str]) -> None: (ROOT / "references" / name).write_text("\n\n".join(sections) + "\n") print(f"wrote references/{name}") @@ -502,36 +158,38 @@ def main() -> None: args.add_argument( "--major", type=int, help="target major, defaults to the declared one" ) - args.add_argument( - "--source-ref", help="git ref for source links, defaults to the tag" - ) opts = args.parse_args() logging.getLogger("griffe").setLevel(logging.ERROR) - pkg = griffe.load("posthog", search_paths=[str(ROOT)], resolve_aliases=False) - declared = (ROOT / "posthog/version.py").read_text().split('"')[1] - major = opts.major or int(declared.split(".")[0]) - # A branch that targets a newer major than it has released documents an experimental one. - status = "current" if int(declared.split(".")[0]) >= major else "experimental" - version = declared if status == "current" else "unreleased" - ref = opts.source_ref or f"v{version}" + pkg = griffe.load( + "posthog", + search_paths=[str(ROOT)], + docstring_parser="google", + resolve_aliases=False, + ) + declared = int( + (ROOT / "posthog/version.py").read_text().split('"')[1].split(".")[0] + ) + major = opts.major or declared started = nest((ROOT / "docs/getting-started.md").read_text()) - reference_md = reference(pkg, capabilities(opts.sdk_specs), ref) - body = ["## Getting started", started, *reference_md] - # Each major gets its own page. The current one is also the latest reference. + body = [ + "## Getting started", + started, + *reference(pkg, capabilities(opts.sdk_specs)), + ] title = f"PostHog Python SDK {major}.x" + ( - "" if status == "current" else " (experimental)" + "" if declared >= major else " (experimental)" ) write(f"posthog-python-{major}.md", [front_matter(title, body), *body]) - if status == "current": + if declared >= major: write( "posthog-python-reference.md", [front_matter("PostHog Python SDK", body), *body], ) - # Newest guide first, so the migration page opens on the latest upgrade. if guides := sorted((ROOT / "docs").glob("migration-*.md"), reverse=True): + head = "---\ntitle: PostHog Python SDK migration guides\nsidebar: Docs\nshowTitle: true\n---" write( "posthog-python-migration.md", - [GUIDES, *(nest(g.read_text(), drop_title=False) for g in guides)], + [head, *(g.read_text().strip() for g in guides)], ) diff --git a/pyproject.toml b/pyproject.toml index d13cde587..ee69e9a0a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,6 +64,7 @@ metrics = [ dev = [ "django-stubs", "griffe", + "griffe2md", "lxml", "mypy", "mypy-baseline", diff --git a/uv.lock b/uv.lock index 83c4611c2..270d74523 100644 --- a/uv.lock +++ b/uv.lock @@ -1392,6 +1392,46 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/c0/2bb018eecf9a83c68db9cd9fffd9dab25f102ad30ed869451046e46d1187/griffe-2.0.2-py3-none-any.whl", hash = "sha256:2b31816460aee1996af26050a1fc6927a2e5936486856707f55508e4c9b5960b", size = 5141, upload-time = "2026-03-27T11:34:47.721Z" }, ] +[[package]] +name = "griffe2md" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "griffelib" }, + { name = "jinja2" }, + { name = "mdformat" }, + { name = "tomli" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/a8/18adadd26454832fb0beaea07e5f05b92f8830a112ec860d3c403914d651/griffe2md-1.5.0.tar.gz", hash = "sha256:58e184dffcd506ca97f54516dc07dd41f98ddc76eb17ba30518cf3dda18f6a51", size = 47309, upload-time = "2026-03-12T16:22:01.392Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/50/75d7902f3a82e77710123418e905302309e116c4d12050f04cf6f4ed9d4e/griffe2md-1.5.0-py3-none-any.whl", hash = "sha256:1becaff032cb02ffedd46057a83c218400bb586579c37010ec8504377108591d", size = 32367, upload-time = "2026-03-12T16:22:00.237Z" }, +] + +[[package]] +name = "griffe2md" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform != 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform != 'win32'", + "python_full_version == '3.12.*'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "griffelib" }, + { name = "jinja2" }, + { name = "mdformat" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/28/588e886370cb55d351895c86e5b9574293769a902b7f722d341f08d357d2/griffe2md-1.5.1.tar.gz", hash = "sha256:6ccf7ed1db2955c70b88eabf404f4f6a2c632d50790157fc3d3b7d269017dffd", size = 50114, upload-time = "2026-10-06T09:10:33.521Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/d3/c45c4e97e10b8ec466d472a35f10a651080ddec636dc805599a305f5d4ac/griffe2md-1.5.1-py3-none-any.whl", hash = "sha256:74ef2ed31926b241186f7c4c5f3f30725e4579468717d4c4f7f1565b41f40f8a", size = 37338, upload-time = "2026-10-06T09:10:32.153Z" }, +] + [[package]] name = "griffecli" version = "2.0.2" @@ -2270,6 +2310,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f5/f4/e58bc33317c92a0203664daaf00bf6f41166cc0149e5d6870a03f7cd004a/mcp-1.30.0-py3-none-any.whl", hash = "sha256:666edb5009503e1047c9d60346a756f94b261f05cc2625f23d41c728ffc484d0", size = 234581, upload-time = "2026-09-07T14:34:14.266Z" }, ] +[[package]] +name = "mdformat" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3f/05/32b5e14b192b0a8a309f32232c580aefedd9d06017cb8fe8fce34bec654c/mdformat-1.0.0.tar.gz", hash = "sha256:4954045fcae797c29f86d4ad879e43bb151fa55dbaf74ac6eaeacf1d45bb3928", size = 56953, upload-time = "2025-10-16T12:05:03.695Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/9a/8fe71b95985ca7a4001effbcc58e5a07a1f2a2884203f74dcf48a3b08315/mdformat-1.0.0-py3-none-any.whl", hash = "sha256:bca015d65a1d063a02e885a91daee303057bc7829c2cd37b2075a50dbb65944b", size = 53288, upload-time = "2025-10-16T12:05:02.607Z" }, +] + [[package]] name = "mdurl" version = "0.1.2" @@ -3222,6 +3275,8 @@ async = [ dev = [ { name = "django-stubs" }, { name = "griffe" }, + { name = "griffe2md", version = "1.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "griffe2md", version = "1.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "lxml" }, { name = "mypy" }, { name = "mypy-baseline" }, @@ -3323,6 +3378,7 @@ requires-dist = [ { name = "gevent", marker = "implementation_name == 'cpython' and extra == 'test'", specifier = ">=25.4.1" }, { name = "google-genai", marker = "extra == 'test'" }, { name = "griffe", marker = "extra == 'dev'" }, + { name = "griffe2md", marker = "extra == 'dev'" }, { name = "httpx", marker = "extra == 'async'", specifier = ">=0.27.0,<1.0" }, { name = "jsonschema", marker = "extra == 'test'", specifier = ">=4.0" }, { name = "langchain", marker = "extra == 'langchain'", specifier = ">=1.3.9" }, From 3f97627659da9418baf821b8a57b5aef23b9246c Mon Sep 17 00:00:00 2001 From: "Vincent (Wen Yu) Ge" Date: Wed, 4 Nov 2026 17:43:27 -0500 Subject: [PATCH 6/8] docs: generate the reference pages with griffe2md Co-Authored-By: Claude Opus 5.5 --- references/posthog-python-7.md | 4102 ++++++++++++++++++++++ references/posthog-python-8.md | 4363 ++++++++++++++++++++++++ references/posthog-python-migration.md | 269 ++ references/posthog-python-reference.md | 4102 ++++++++++++++++++++++ 4 files changed, 12836 insertions(+) create mode 100644 references/posthog-python-7.md create mode 100644 references/posthog-python-8.md create mode 100644 references/posthog-python-migration.md create mode 100644 references/posthog-python-reference.md diff --git a/references/posthog-python-7.md b/references/posthog-python-7.md new file mode 100644 index 000000000..53276c162 --- /dev/null +++ b/references/posthog-python-7.md @@ -0,0 +1,4102 @@ +--- +title: "PostHog Python SDK 7.x" +sidebar: Docs +showTitle: true +contentMaxWidthClass: max-w-full +platformLogo: python +tableOfContents: + - value: "Getting started" + url: getting-started + depth: 0 + - value: "Traces" + url: traces + depth: 0 + - value: "Alias" + url: alias + depth: 0 + - value: "Capture" + url: capture + depth: 0 + - value: "Capture AI" + url: capture-ai + depth: 0 + - value: "Capture exception" + url: capture-exception + depth: 0 + - value: "Emails" + url: emails + depth: 0 + - value: "Flush" + url: flush + depth: 0 + - value: "Get feature flag" + url: get-feature-flag + depth: 0 + - value: "Get feature flag payload" + url: get-feature-flag-payload + depth: 0 + - value: "Get feature flag result" + url: get-feature-flag-result + depth: 0 + - value: "Get feature flags" + url: get-feature-flags + depth: 0 + - value: "Get feature flags and payloads" + url: get-feature-flags-and-payloads + depth: 0 + - value: "Evaluate flags" + url: evaluate-flags + depth: 0 + - value: "Group identify" + url: group-identify + depth: 0 + - value: "Identify" + url: identify + depth: 0 + - value: "Is feature enabled" + url: is-feature-enabled + depth: 0 + - value: "Setup" + url: setup + depth: 0 + - value: "Shutdown" + url: shutdown + depth: 0 + - value: "Flag definition loader" + url: flag-definition-loader + depth: 0 + - value: "SDK utils" + url: sdk-utils + depth: 0 + - value: "AsyncClient" + url: asyncclient + depth: 0 + - value: "Module functions" + url: module-functions + depth: 0 + - value: "Types" + url: types + depth: 0 +--- + +## Getting started + + +The Python SDK captures events, evaluates feature flags and reports errors from Python 3.10 or later. + +### Install + +```bash +pip install posthog +``` + +### Initialize + +Create one client for your app with your project token and host. Your project token and host are in your [project settings](https://app.posthog.com/project/settings). Keep the token in an environment variable rather than in code. + +```python +from posthog import Posthog + +posthog = Posthog("", host="") +``` + +### Capture an event + +```python +posthog.capture("user_signed_up", distinct_id="user_123", properties={"plan": "free"}) +``` + +`capture` queues the event and returns right away. A background thread sends it. + +### Shut down + +Call `shutdown()` before your process exits, so queued events are sent. + +```python +posthog.shutdown() +``` + +In an asyncio app, use `AsyncPosthog` as an async context manager instead. See [AsyncClient](#asyncclient). + +## Traces + +### `Client.start_span` + +```python +start_span(name: str, *, kind: Optional[str] = None, attributes: Optional[Mapping[str, Any]] = None, parent: Union[Span, str, None] = None, tracestate: Optional[str] = None, start_time: Union[datetime, float, None] = None) -> Span +``` + +Start a span for distributed tracing. Alpha. + +Returns a span handle. Use it as a context manager to make it the active +span for the block and end it on exit (recording a raised exception on +the way out); or call `end()` yourself for a span that cannot wrap a +block. Spans started inside the block nest under it automatically. +Always returns a usable handle, even when tracing is off, so calling +code never branches. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`name` | str | A low-cardinality operation name, e.g. `GET /users/:id`. Variable values belong in attributes, not the name. | *required* +`kind` | Optional[str] | `internal` (default), `server`, `client`, `producer` or `consumer`. | None +`attributes` | Optional[Mapping[str, Any]] | Initial attributes. | None +`parent` | Union[Span, str, None] | A span handle, or an inbound W3C `traceparent` header value to continue a remote trace. Defaults to the active span. A forked child starts with no active span; pass the parent span to continue a trace across a fork. | None +`tracestate` | Optional[str] | The inbound `tracestate` header accompanying a `traceparent` string `parent`; preserved and propagated. | None +`start_time` | Union[datetime, float, None] | A `datetime` or epoch seconds, to backdate the span. | None + +**Examples:** + +```python +posthog = Posthog("", traces={"service_name": "checkout-api"}) + +with posthog.start_span("POST /checkout", parent=request.headers.get("traceparent")) as span: + span.set_attribute("plan", user.plan) + with posthog.start_span("db.query", kind="client"): + ... + outgoing_headers = {"traceparent": span.traceparent()} +``` + +### `Client.get_active_span` + +```python +get_active_span() -> Optional[Span] +``` + +The span that is active in the current context, or `None`. Alpha. + +Only entering a span (`with posthog.start_span(...) as span:`) makes +it active; a span started manually is not. Use it to propagate the +trace to the next service: `span.traceparent()` is the header value. + +**Examples:** + +```python +span = posthog.get_active_span() +if span is not None: + headers["traceparent"] = span.traceparent() +``` + +## Alias + +### `Client.alias` + +```python +alias(previous_id: ID_TYPES, distinct_id: Optional[str], timestamp: Optional[Union[datetime, str]] = None, uuid: Optional[str] = None, disable_geoip: Optional[bool] = None) -> Optional[str] +``` + +Create an alias between two distinct IDs. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`previous_id` | ID_TYPES | The previous distinct ID. Required - the call is dropped with a warning if it is missing or empty. | *required* +`distinct_id` | Optional[str] | The new distinct ID to alias to. Falls back to the context distinct ID; the call is dropped with a warning if neither is available. | *required* +`timestamp` | Optional[Union[datetime, str]] | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | None +`uuid` | Optional[str] | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | None +`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this event. | None + +**Examples:** + +```python +posthog.alias(previous_id='distinct_id', distinct_id='alias_id') +``` + +Note: This method will not raise exceptions. Errors are logged. + +## Capture + +### `Client.capture` + +```python +capture(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] +``` + +Captures an event manually. [Learn about capture best practices](https://posthog.com/docs/product-analytics/capture-events) + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`event` | str | The event name to capture. | *required* +`distinct_id` | | The distinct ID of the user. | +`properties` | | A dictionary of properties to include with the event. | +`timestamp` | | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | +`uuid` | | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | +`groups` | | A dictionary of group information. | +`flags` | | A FeatureFlagEvaluations snapshot from evaluate_flags(). The exact values from the snapshot are attached with no extra /flags request. | +`send_feature_flags` | | Deprecated. Prefer flags=... from evaluate_flags(). When truthy, evaluates flags during capture and attaches them to the event. | +`disable_geoip` | | Whether to disable GeoIP for this event. | + +**Examples:** + +```python +# Anonymous event +posthog.capture('some-anon-event') +``` +```python +# Context usage +from posthog import identify_context, new_context +with new_context(): + identify_context('distinct_id_of_the_user') + posthog.capture('user_signed_up') + posthog.capture('user_logged_in') + posthog.capture('some-custom-action', distinct_id='distinct_id_of_the_user') +``` +```python +# Set event properties +posthog.capture( + "user_signed_up", + distinct_id="distinct_id_of_the_user", + properties={ + "login_type": "email", + "is_free_trial": "true" + } +) +``` +```python +# Page view event +posthog.capture('$pageview', distinct_id="distinct_id_of_the_user", properties={'$current_url': 'https://example.com'}) +``` + +## Capture AI + +### `Client.capture_ai` + +```python +capture_ai(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] +``` + +Capture an AI event on the dedicated AI capture endpoint. + +Beta: the signature is stable; operational limits (per-event size +cap, batching, endpoint) may change without notice. + +Takes the same arguments and returns the same value as `capture()`: +the event UUID, or None when the event was not admitted (disabled +client, or dropped by `before_send`). The event is queued on an +isolated AI lane with its own consumer pool and a higher per-event +size cap, posting to the dedicated AI ingestion endpoint. The payload +is sent as given — no redaction or truncation is applied here. + +## Capture exception + +### `Client.capture_exception` + +```python +capture_exception(exception: Optional[ExceptionArg], **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] +``` + +Capture an exception for error tracking. + +When OpenTelemetry is installed and a valid span is active, its trace and +span IDs are added as `$trace_id` and `$span_id` event properties. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`exception` | Optional[ExceptionArg] | The exception to capture. | *required* +`distinct_id` | | The distinct ID of the user. | +`properties` | | A dictionary of additional properties. Overriding reserved exception properties is deprecated and will stop working in the next major version. | +`flags` | | A `FeatureFlagEvaluations` snapshot from `evaluate_flags()`. Attaches those exact flag values to the captured `$exception` event. | +`send_feature_flags` | | Deprecated. Pass `flags` from `evaluate_flags()` instead. | +`disable_geoip` | | Whether to disable GeoIP for this event. | + +**Examples:** + +```python +try: + # Some code that might fail + pass +except Exception as e: + posthog.capture_exception(e, 'user_distinct_id', properties=additional_properties) +``` + +## Emails + +### `Client.emails` + +```python +emails: Emails +``` + +Send transactional email from PostHog email templates, and read the +status of each email sent with `get`. + +Needs `secret_key`, a project secret API key with the `hog_flow:write` +scope. Sends go to `app_host`, or to the PostHog Cloud app host of +`host`. `email_request_timeout_seconds` and `email_request_max_retries` +configure each send. A disabled or shut-down client sends nothing. See +`posthog.emails` for setup. + +**Examples:** + +```python +client.emails.send( + to="user@example.com", + template_id="0190f6d2-7c1e-7b3a-9f5e-1c2d3e4f5a6b", + template_data={"name": "Ada"}, + distinct_id="user_123", +) +``` + +## Flush + +### `Client.flush` + +```python +flush(timeout_seconds: Optional[float] = 10) -> None +``` + +Attempt immediate delivery of queued events, ended spans and autocaptured metrics, keeping the client reusable. + +For a client shared across requests or serverless invocations (including +the module-level client), call `flush()` before a short-lived invocation +returns. Use `shutdown()` instead only for final process cleanup or when +disposing of a client that will not be reused. Flush does not stop the +background workers or feature flag poller, and does not end open spans. + +Returning does not guarantee server receipt: pending work can remain after +a timeout or span export failure, and failed events may be dropped by the +consumers. Calls made directly from SDK callbacks such as `on_error` are +deferred rather than blocking the worker that invoked the callback. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`timeout_seconds` | Optional[float] | Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass `None` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently. The span exporter waits up to this many seconds for an in-flight flush, then uses a fresh drain budget once it acquires the lock. If the lock wait times out, the in-flight flush is left to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. Each span request is bounded by the client's `timeout` in seconds, and the last request is not cut short. This is therefore not a strict wall-clock limit for the whole call. With `None`, a retriable span failure is left for a later flush rather than retried within this call. | 10 + +**Examples:** + +```python +posthog.capture('event_name', distinct_id='user_id') +# Before a serverless invocation returns; keep the shared client open. +posthog.flush(timeout_seconds=10) +``` + +## Get feature flag + +### `Client.get_feature_flag` + +```python +get_feature_flag(key: str, distinct_id: ID_TYPES, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[FlagValue] +``` + +Get multivariate feature flag value for a user. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The feature flag key. | *required* +`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None +`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None +`only_evaluate_locally` | bool | Whether to only evaluate locally. | False +`send_feature_flag_events` | bool | Whether to send feature flag events. | True +`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None +`device_id` | Optional[str] | The device ID for this request. | None + +**Examples:** + +```python +enabled_variant = posthog.get_feature_flag('flag-key', 'distinct_id_of_your_user') +if enabled_variant == 'variant-key': # replace 'variant-key' with the key of your variant + # Do something differently for this user + # Optional: fetch the payload + matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user') +``` + +## Get feature flag payload + +### `Client.get_feature_flag_payload` + +```python +get_feature_flag_payload(key: str, distinct_id: ID_TYPES, *, match_value: Optional[FlagValue] = None, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = False, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[object] +``` + +Get the payload for a feature flag. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The feature flag key. | *required* +`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* +`match_value` | Optional[FlagValue] | The specific flag value to get payload for. | None +`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None +`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None +`only_evaluate_locally` | bool | Whether to only evaluate locally. | False +`send_feature_flag_events` | bool | Deprecated. Use get_feature_flag() instead if you need events. | False +`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None +`device_id` | Optional[str] | The device ID for this request. | None + +**Returns:** + +Type | Description +---- | ----------- +Optional[object] | The payload associated with the matched feature flag value, or None. +Optional[object] | This method returns the payload only, not the FeatureFlagResult wrapper +Optional[object] | used internally to compute it. + +**Examples:** + +```python +is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user') + +if is_my_flag_enabled: + # Do something differently for this user + # Optional: fetch the payload + matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user') +``` + +## Get feature flag result + +### `Client.get_feature_flag_result` + +```python +get_feature_flag_result(key: str, distinct_id: ID_TYPES, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[FeatureFlagResult] +``` + +Get a FeatureFlagResult object which contains the flag result and payload for a key by evaluating locally or remotely +depending on whether local evaluation is enabled and the flag can be locally evaluated. +This also captures the `$feature_flag_called` event unless `send_feature_flag_events` is `False`. + +**Examples:** + +```python +flag_result = posthog.get_feature_flag_result('flag-key', 'distinct_id_of_your_user') +if flag_result and flag_result.get_value() == 'variant-key': + # Do something differently for this user + # Optional: fetch the payload + matched_flag_payload = flag_result.payload +``` + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The feature flag key. | *required* +`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None +`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None +`only_evaluate_locally` | bool | Whether to only evaluate locally. | False +`send_feature_flag_events` | bool | Whether to send feature flag events. | True +`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None +`device_id` | Optional[str] | The device ID for this request. | None + +**Returns:** + +Type | Description +---- | ----------- +Optional[FeatureFlagResult] | Optional[FeatureFlagResult]: The feature flag result or None if disabled/not found. + +## Get feature flags + +### `Client.get_feature_variants` + +```python +get_feature_variants(distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, disable_geoip: Optional[bool] = None, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None) -> dict[str, Union[bool, str]] +``` + +Get feature flag variants for a user. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None +`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None +`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None +`flag_keys_to_evaluate` | Optional[list[str]] | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | None +`device_id` | Optional[str] | The device ID for this request. | None + +### `Client.get_all_flags` + +```python +get_all_flags(distinct_id: ID_TYPES, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None) -> Optional[dict[str, Union[bool, str]]] +``` + +Get all feature flags for a user. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None +`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None +`only_evaluate_locally` | bool | Whether to only evaluate locally. | False +`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None +`flag_keys_to_evaluate` | Optional[list[str]] | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | None +`device_id` | Optional[str] | The device ID for this request. | None + +**Examples:** + +```python +posthog.get_all_flags('distinct_id_of_your_user') +``` + +## Get feature flags and payloads + +### `Client.get_feature_flags_and_payloads` + +```python +get_feature_flags_and_payloads(distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, disable_geoip: Optional[bool] = None, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None) -> FlagsAndPayloads +``` + +Get feature flags and payloads for a user. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None +`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None +`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None +`flag_keys_to_evaluate` | Optional[list[str]] | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | None +`device_id` | Optional[str] | The device ID for this request. | None + +**Examples:** + +```python +result = posthog.get_feature_flags_and_payloads('') +``` + +### `Client.get_all_flags_and_payloads` + +```python +get_all_flags_and_payloads(distinct_id: ID_TYPES, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None) -> FlagsAndPayloads +``` + +Get all feature flags and their payloads for a user. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None +`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None +`only_evaluate_locally` | bool | Whether to only evaluate locally. | False +`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None +`flag_keys_to_evaluate` | Optional[list[str]] | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | None +`device_id` | Optional[str] | The device ID for this request. | None + +**Examples:** + +```python +posthog.get_all_flags_and_payloads('distinct_id_of_your_user') +``` + +## Evaluate flags + +### `Client.evaluate_flags` + +```python +evaluate_flags(distinct_id: Optional[ID_TYPES] = None, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, flag_keys: Optional[List[str]] = None, device_id: Optional[str] = None) -> FeatureFlagEvaluations +``` + +Evaluate all feature flags for a user in a single call and return a +`FeatureFlagEvaluations` snapshot. Branch on `.is_enabled()` / +`.get_flag()` and pass the same snapshot to `capture` via the +`flags` option so events carry the exact flag values the code branched on. + +Prefer this over repeated `get_feature_flag()` calls and over +`capture(send_feature_flags=True)` — it consolidates flag evaluation into +a single `/flags` request per incoming request. + +Local evaluation is transparent: when the poller resolves a flag, the +snapshot's `$feature_flag_called` events are tagged `locally_evaluated=True` +and reason `"Evaluated locally"`. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | Optional[ID_TYPES] | The user's distinct ID. If `None`, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot. | None +`groups` | Optional[Mapping[str, Union[str, int]]] | Mapping of group type to group key. | None +`person_properties` | Optional[Dict[str, Any]] | Person properties to use for evaluation. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties keyed by group type. | None +`only_evaluate_locally` | bool | If True, never fall back to remote evaluation — flags that can't be evaluated locally are omitted from the snapshot and listed with a reason in its `unresolved_flags`. | False +`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup. | None +`flag_keys` | Optional[List[str]] | Optional list that scopes local evaluation, the underlying `/flags` request, and the returned snapshot. When omitted or `None`, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per `evaluate_flags` call unless `only_evaluate_locally` is True. If the server also does not know the key, it is omitted from the snapshot. | None +`device_id` | Optional[str] | Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes. | None + +**Returns:** + +Type | Description +---- | ----------- +FeatureFlagEvaluations | A `FeatureFlagEvaluations` snapshot. + +**Examples:** + +```python +flags = posthog.evaluate_flags( + "user_123", + person_properties={"plan": "enterprise"}, +) +if flags.is_enabled("new-dashboard"): + render_new_dashboard() +posthog.capture("page_viewed", distinct_id="user_123", flags=flags) +``` + +## Group identify + +### `Client.group_identify` + +```python +group_identify(group_type: str, group_key: str, properties: Optional[Dict[str, Any]] = None, timestamp: Optional[Union[datetime, str]] = None, uuid: Optional[Union[str, UUID]] = None, disable_geoip: Optional[bool] = None, distinct_id: Optional[ID_TYPES] = None) -> Optional[str] +``` + +Identify a group and set its properties. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`group_type` | str | The type of group (e.g., 'company', 'team'). Required - the call is dropped with a warning if it is missing or empty. | *required* +`group_key` | str | The unique identifier for the group. Required - the call is dropped with a warning if it is missing or empty. | *required* +`properties` | Optional[Dict[str, Any]] | A dictionary of properties to set on the group. | None +`timestamp` | Optional[Union[datetime, str]] | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | None +`uuid` | Optional[Union[str, UUID]] | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | None +`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this event. | None +`distinct_id` | Optional[ID_TYPES] | The distinct ID of the user performing the action. | None + +**Examples:** + +```python +posthog.group_identify('company', 'company_id_in_your_db', { + 'name': 'Awesome Inc.', + 'employees': 11 +}) +``` + +Note: This method will not raise exceptions. Errors are logged. + +## Identify + +### `Client.set` + +```python +set(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str] +``` + +Set properties on a person profile. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | | The distinct ID of the user. | +`properties` | | A dictionary of properties to set. | +`timestamp` | | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | +`uuid` | | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | +`disable_geoip` | | Whether to disable GeoIP for this event. | + +**Examples:** + +```python +# Set with distinct id +posthog.set(distinct_id='user123', properties={'name': 'Max Hedgehog'}) +``` + +Note: This method will not raise exceptions. Errors are logged. + +### `Client.set_once` + +```python +set_once(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str] +``` + +Set properties on a person profile only if they haven't been set before. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | | The distinct ID of the user. | +`properties` | | A dictionary of properties to set once. | +`timestamp` | | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | +`uuid` | | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | +`disable_geoip` | | Whether to disable GeoIP for this event. | + +**Examples:** + +```python +posthog.set_once(distinct_id='user123', properties={'initial_signup_date': '2024-01-01'}) +``` + +Note: This method will not raise exceptions. Errors are logged. + +## Is feature enabled + +### `Client.feature_enabled` + +```python +feature_enabled(key: str, distinct_id: ID_TYPES, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[bool] +``` + +Check if a feature flag is enabled for a user. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The feature flag key. | *required* +`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None +`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None +`only_evaluate_locally` | bool | Whether to only evaluate locally. | False +`send_feature_flag_events` | bool | Whether to send feature flag events. | True +`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None +`device_id` | Optional[str] | The device ID for this request. | None + +**Examples:** + +```python +is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user') +if is_my_flag_enabled: + # Do something differently for this user + # Optional: fetch the payload + matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user') +``` + +## Setup + +### `Client(...)` + +```python +__init__(project_api_key: str, host: str = None, debug: str = False, max_queue_size: str = 10000, send: str = True, on_error: str = None, flush_at: str = 100, flush_interval: str = 5.0, gzip: str = False, max_retries: str = 3, sync_mode: str = False, timeout: float = 15, thread: float = 1, poll_interval: float = 30, personal_api_key: float = None, disabled: float = False, disable_geoip: float = True, is_server: float = True, historical_migration: float = False, feature_flags_request_timeout_seconds: float = 3, feature_flags_request_max_retries: float = 1, super_properties: float = None, enable_exception_autocapture: float = False, log_captured_exceptions: float = False, project_root: float = None, privacy_mode: float = False, before_send: float = None, flag_fallback_cache_url: float = None, enable_local_evaluation: float = True, flag_definition_cache_provider: Optional[FlagDefinitionCacheProvider] = None, capture_exception_code_variables: Optional[FlagDefinitionCacheProvider] = False, code_variables_mask_patterns: Optional[FlagDefinitionCacheProvider] = None, code_variables_ignore_patterns: Optional[FlagDefinitionCacheProvider] = None, code_variables_mask_url_credentials: Optional[FlagDefinitionCacheProvider] = None, code_variables_detect_secrets: Optional[FlagDefinitionCacheProvider] = None, in_app_modules: list[str] | None = None, enable_exception_autocapture_rate_limiting: list[str] | None = False, exception_autocapture_bucket_size: list[str] | None = ExceptionCapture.DEFAULT_BUCKET_SIZE, exception_autocapture_refill_rate: list[str] | None = ExceptionCapture.DEFAULT_REFILL_RATE, exception_autocapture_refill_interval_seconds: list[str] | None = ExceptionCapture.DEFAULT_REFILL_INTERVAL_SECONDS, capture_mode: Optional[Union[CaptureMode, str]] = None, capture_compression: Optional[Union[CaptureCompression, str]] = None, secret_key: Optional[Union[CaptureCompression, str]] = None, metrics: Optional[dict] = None, enable_full_ai_capture: Optional[dict] = False, capture_trace_context: Optional[dict] = False, _use_ai_lane: Optional[dict] = False, _enable_multimodal_capture: Optional[dict] = False, traces: Optional[dict] = None, app_host: Optional[str] = None, email_request_timeout_seconds: float = 15, email_request_max_retries: int = 2) +``` + +Initialize a new PostHog client instance. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`project_api_key` | str | PostHog project API key/token. | *required* +`host` | | PostHog host. Defaults to the US ingestion endpoint when not set. App hosts such as `https://us.posthog.com` are mapped to the corresponding ingestion host. | None +`debug` | | Enable verbose SDK logging and re-raise errors from public API methods. | False +`max_queue_size` | | Maximum number of events buffered before upload. | 10000 +`send` | | If False, queueing succeeds but events are not sent. | True +`on_error` | | Optional callback invoked by background consumers when an upload fails. Keep it short and non-blocking. Calling lifecycle methods directly is safe and deferred, but do not start another thread or task that calls `flush()`, `join()`, or `shutdown()` and then wait for it from the callback. | None +`flush_at` | | Number of queued events that triggers a batch upload. | 100 +`flush_interval` | | Maximum seconds a background consumer waits before flushing a partial batch. | 5.0 +`gzip` | | Whether to gzip event upload payloads. | False +`max_retries` | | Number of upload retries. Values below 0 are treated as 0. | 3 +`sync_mode` | | If True, send each event synchronously instead of using background worker threads. This blocks the calling thread; in asyncio applications such as FastAPI, use `AsyncPosthog` instead. | False +`timeout` | float | HTTP request timeout in seconds for event uploads. | 15 +`thread` | | Number of background consumer threads. | 1 +`poll_interval` | | Seconds between local feature flag definition refreshes. | 30 +`secret_key` | | A Personal API Key or Project Secret API Key, used to authenticate local feature flag evaluation, remote config payloads, and decrypted flag payloads. Example::
posthog.Client(project_api_key, secret_key="phx_...") | None +`personal_api_key` | | Deprecated alias for `secret_key`. Still honored for backwards compatibility; prefer `secret_key`, which also accepts a Project Secret API Key. | None +`disabled` | | If True, disable captures and API requests. Useful in tests. | False +`disable_geoip` | | Whether to disable server-side GeoIP enrichment. Defaults to True. | True +`is_server` | | Whether events are emitted from a server-side runtime. Defaults to True; set to False when using the SDK as a client/CLI so the device OS is attributed to the person normally. | True +`historical_migration` | | Mark events as historical migration imports. | False +`feature_flags_request_timeout_seconds` | float | Timeout in seconds for feature flag and remote config requests. | 3 +`feature_flags_request_max_retries` | | Number of retries for feature flag requests after network, transport, or timeout failures. Defaults to 1. Set to 0 to disable retries. | 1 +`app_host` | Optional[str] | Your PostHog app host, such as `https://posthog.example.com`. `emails` sends there. Needed only when `host` is not PostHog Cloud, for example a reverse proxy, which must not receive the secret key. | None +`email_request_timeout_seconds` | float | Timeout in seconds for each email send request. Defaults to 15. | 15 +`email_request_max_retries` | int | Number of retries for an email send after a network error, a rate limit, or an unavailable service. Defaults to 2. Set to 0 when a job runner already retries the send. | 2 +`super_properties` | | Properties merged into every captured event. | None +`enable_exception_autocapture` | | Automatically capture uncaught exceptions. | False +`log_captured_exceptions` | | Also log exceptions captured by error tracking. | False +`project_root` | | Root path used to determine in-app stack frames for captured exceptions. Defaults to the current working directory. | None +`privacy_mode` | | For AI observability, capture usage metadata without prompt inputs or outputs. | False +`enable_full_ai_capture` | | Route PostHog AI wrapper events through the dedicated AI capture endpoint and capture full AI content: skips string truncation and passes media (base64/data URIs) through unredacted. `privacy_mode` always wins. Defaults to False. | False +`before_send` | | Optional callback that can modify or drop events before upload. Return `None` to drop an event. | None +`flag_fallback_cache_url` | | Optional feature flag fallback cache URL, such as `memory://local/?ttl=300&size=10000` or a Redis URL. | None +`enable_local_evaluation` | | Whether to poll feature flag definitions for local evaluation when a personal API key is configured. | True +`flag_definition_cache_provider` | Optional[FlagDefinitionCacheProvider] | Optional external cache provider for sharing feature flag definitions across workers. | None +`capture_exception_code_variables` | | Capture local variable values on exception stack frames. | False +`capture_trace_context` | | When OpenTelemetry is installed and a valid span is active at capture time, add its trace and span IDs as `$trace_id` and `$span_id` properties to events captured with `capture()` and `capture_ai()`, so they can be correlated with backend traces. Explicit `$trace_id`/`$span_id` values passed in `properties` win. Exception events (`capture_exception`) always attach these IDs regardless of this setting. Defaults to False. | False +`traces` | Optional[dict] | Config dict for distributed tracing: `service_name`, `service_version`, `environment`, `resource_attributes`, `flush_interval` (5 s), `max_queue_size` (2048), `max_export_batch_size` (512), `max_live_spans` (10000), `max_span_age` (3600 s), `max_attributes_per_span` (128), `max_events_per_span` (128), `max_attribute_value_length` (8192). `before_span_send` is a callable, or a list run in order, that receives each finished span as a dict (`trace_id`, `span_id` and `parent_span_id` are read-only) and returns it, edited, or `None` to drop it; a hook that raises drops the span. Tracing is off until this is provided. Spans export on a background timer even with `sync_mode`; serverless handlers should call `flush()` before returning. Defaults to None. | None +`code_variables_mask_patterns` | | Variable-name patterns to mask when capturing code variables. | None +`code_variables_ignore_patterns` | | Variable-name patterns to omit when capturing code variables. | None +`code_variables_mask_url_credentials` | | Scrub credentials embedded in URLs/DSNs (e.g. `user:pass@host`) from captured code variables, regardless of the surrounding variable name. Defaults to True. | None +`code_variables_detect_secrets` | | Last-resort entropy-based detection that redacts high-entropy secret-looking values (API keys, tokens, strong passwords) sitting in innocuously-named variables, after the name and URL checks. Skips structured ids (UUIDs, ObjectIds, hashes). Defaults to True. | None +`in_app_modules` | list[str] \| None | Module/package prefixes treated as in-app frames in captured exceptions. | None +`enable_exception_autocapture_rate_limiting` | | Rate limit autocaptured exceptions client-side with a token bucket per exception type. Disabled by default. | False +`exception_autocapture_bucket_size` | | Maximum burst of autocaptured exceptions allowed per exception type (token bucket size, clamped to 0-100). | DEFAULT_BUCKET_SIZE +`exception_autocapture_refill_rate` | | Tokens restored per refill interval for each exception type's bucket. | DEFAULT_REFILL_RATE +`exception_autocapture_refill_interval_seconds` | | Seconds between token refills for autocaptured exception rate limiting. | DEFAULT_REFILL_INTERVAL_SECONDS +`capture_mode` | Optional[Union[CaptureMode, str]] | Capture wire protocol to use. Defaults to `CaptureMode.V0` (legacy `/batch/`). Set `CaptureMode.V1` (or pass the string `"v1"`) to opt into `/i/v1/analytics/events`. When omitted, the `POSTHOG_CAPTURE_MODE` env var is consulted, then `V0`. | None +`capture_compression` | Optional[Union[CaptureCompression, str]] | Request-body compression for capture-v1 uploads (ignored in V0, which uses `gzip`). `CaptureCompression.GZIP` or `DEFLATE` (or the strings `"gzip"`/`"deflate"`). When omitted, the `POSTHOG_CAPTURE_COMPRESSION` env var is consulted, then the legacy `gzip` flag, then no compression. | None + +**Examples:** + +```python +from posthog import Posthog + +posthog = Posthog('', host='') +``` + +## Shutdown + +### `Client.join` + +```python +join() -> None +``` + +Attempt to process queued events and end the consumer threads. Do not use directly, call `shutdown()` instead. + +Failed or undrainable events may be dropped and reported through logging +or `on_error`; returning does not guarantee server receipt. Lifecycle +cleanup is attempted once, and cleanup failures are logged without retry. + +**Examples:** + +```python +posthog.join() +``` + +### `Client.shutdown` + +```python +shutdown() -> None +``` + +Attempt final delivery and permanently shut down the client. + +Call this for final process cleanup or when disposing of a client that +will not be reused. It closes capture lanes, stops background workers and +the feature flag poller, and tears down integrations. Do not call it at the +end of each request or serverless invocation when the client is shared; +call `flush()` before returning instead. A shut-down client cannot be +reused; create a new client if needed. + +Normally this method blocks until queued events have been attempted and +cleanup finishes. Failed or undrainable events may be dropped and +reported through logging or `on_error`; returning does not guarantee +server receipt. Event draining has no timeout. Queued spans get one final +flush with up to 30 seconds waiting for an in-flight flush, then a fresh +30-second drain budget; the last HTTP request is not cut short. Any spans +it cannot send are discarded with a warning, as are spans still open. +Lifecycle cleanup is attempted once, and cleanup failures +are logged without retry. When called directly from an SDK callback such as +`on_error`, shutdown is deferred to avoid blocking the worker that +invoked the callback. If the callback must coordinate a blocking +shutdown, have it signal an +application-owned thread and return before that thread calls shutdown. +Do not wait inside the callback for another thread or task that calls a +lifecycle method. + +**Examples:** + +```python +posthog.shutdown() +``` + +## Flag definition loader + +### `Client.load_feature_flags` + +```python +load_feature_flags() +``` + +Load feature flags for local evaluation. + +**Examples:** + +```python +posthog.load_feature_flags() +``` + +### `Client.feature_flag_definitions` + +```python +feature_flag_definitions() +``` + +Return feature flag definitions loaded for local evaluation. + +**Returns:** + +Type | Description +---- | ----------- + | The currently loaded feature flag definitions, or `None` before + | local evaluation has loaded definitions. + +## SDK utils + +### `Client.queue` + +```python +queue: Queue +``` + +The analytics lane's queue (kept for backwards compatibility). + +### `Client.consumers` + +```python +consumers: Optional[List[Consumer]] +``` + +Flat list of the lanes' consumers, analytics first (kept for backwards compatibility). + +### `Client.new_context` + +```python +new_context(fresh = False, capture_exceptions: Optional[bool] = None) +``` + +Create a new context for managing shared state. Learn more about [contexts](/docs/libraries/python#contexts). + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`fresh` | | Whether to create a fresh context that doesn't inherit from parent. | False +`capture_exceptions` | Optional[bool] | Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting. | None + +**Examples:** + +```python +with client.new_context(): + client.identify_context('') + client.capture('event_name') +``` + +
+Category + +Contexts + +
+ +### `Client.scoped` + +```python +scoped(fresh = False, capture_exceptions: Optional[bool] = None) +``` + +Decorator that creates a new context for the wrapped function using this client. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`fresh` | | Whether to create a fresh context that doesn't inherit from parent. | False +`capture_exceptions` | Optional[bool] | Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting. | None + +
+Category + +Contexts + +
+ +### `Client.tag` + +```python +tag(name: str, value: Any) -> None +``` + +Add a tag to the current context. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`name` | str | The tag key. | *required* +`value` | Any | The tag value. | *required* + +
+Category + +Contexts + +
+ +### `Client.get_tags` + +```python +get_tags() -> Dict[str, Any] +``` + +Get all tags from the current context. + +**Returns:** + +Type | Description +---- | ----------- +Dict[str, Any] | Dict of all tags in the current context. + +
+Category + +Contexts + +
+ +### `Client.identify_context` + +```python +identify_context(distinct_id: str) -> None +``` + +Identify the current context with a distinct ID. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | str | The distinct ID to associate with the current context and its children. | *required* + +
+Category + +Contexts + +
+ +### `Client.set_context_session` + +```python +set_context_session(session_id: str) -> None +``` + +Set the session ID for the current context. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`session_id` | str | The session ID to associate with the current context and its children. | *required* + +
+Category + +Contexts + +
+ +### `Client.set_context_device_id` + +```python +set_context_device_id(device_id: str) -> None +``` + +Set the device ID for the current context. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`device_id` | str | The device ID to associate with the current context and its children. | *required* + +
+Category + +Contexts + +
+ +### `Client.feature_flags` + +```python +feature_flags +``` + +Get the local evaluation feature flags. + +### `Client.get_feature_payloads` + +```python +get_feature_payloads(distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, disable_geoip: Optional[bool] = None, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None) -> dict[str, Optional[str]] +``` + +Get feature flag payloads for a user, preserving valid serialized JSON. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None +`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None +`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None +`flag_keys_to_evaluate` | Optional[list[str]] | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | None +`device_id` | Optional[str] | The device ID for this request. | None + +**Examples:** + +```python +payloads = posthog.get_feature_payloads('') +``` + +
+Category + +Feature flags + +
+ +### `Client.get_flags_decision` + +```python +get_flags_decision(distinct_id: Optional[ID_TYPES] = None, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, disable_geoip: Optional[bool] = None, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None) -> FlagsResponse +``` + +Get feature flags decision. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | Optional[ID_TYPES] | The distinct ID of the user. | None +`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None +`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None +`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None +`flag_keys_to_evaluate` | Optional[list[str]] | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | None +`device_id` | Optional[str] | The device ID for this request. | None + +**Examples:** + +```python +decision = posthog.get_flags_decision('user123') +``` + +
+Category + +Feature flags + +
+ +### `Client.metrics` + +```python +metrics: PostHogMetrics +``` + +The `posthog.metrics` API: a statsd-style pre-aggregating metrics client — alpha. + +Samples fold into per-series aggregates in memory and flush as one OTLP +data point per series per window, so recording from hot paths is cheap. +Configure via the `metrics` client option; pending metrics flush on +`shutdown()`. + +**Examples:** + +```python +client = Posthog("", metrics={"service_name": "billing-worker"}) +client.metrics.count("invoices.processed", 1, attributes={"plan": "pro"}) +client.metrics.gauge("queue.depth", 42) +client.metrics.histogram("job.duration", 187, unit="ms") +``` + +### `Client.get_remote_config_payload` + +```python +get_remote_config_payload(key: str) +``` + +Get the payload for a remote config feature flag. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The remote config feature flag key. | *required* + +**Returns:** + +Type | Description +---- | ----------- + | The payload associated with the feature flag, or `None` if the + | client is disabled, no personal API key is configured, or the request + | fails. Encrypted payloads are decrypted by PostHog before being + | returned. + +
+Note + +Requires `secret_key` for authentication. + +
+ +
+Category + +Feature flags + +
+ +### `Client.get_feature_flag_evaluation_runtime` + +```python +get_feature_flag_evaluation_runtime(key: str) -> Optional[FeatureFlagEvaluationRuntime] +``` + +Return where a locally loaded feature flag is meant to be evaluated. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The feature flag key. | *required* + +**Returns:** + +Type | Description +---- | ----------- +Optional[FeatureFlagEvaluationRuntime] | The flag's evaluation runtime, or `None` when local evaluation has +Optional[FeatureFlagEvaluationRuntime] | not loaded a definition for this key. A definition that carries no +Optional[FeatureFlagEvaluationRuntime] | runtime reports `FeatureFlagEvaluationRuntime.ALL`, the default +Optional[FeatureFlagEvaluationRuntime] | PostHog applies. + +**Examples:** + +```python +from posthog import FeatureFlagEvaluationRuntime + +runtime = posthog.get_feature_flag_evaluation_runtime("my-flag") +if runtime is FeatureFlagEvaluationRuntime.SERVER: + ... +``` + +
+Category + +Feature flags + +
+ +### `Client.get_feature_flag_keys_by_evaluation_runtime` + +```python +get_feature_flag_keys_by_evaluation_runtime(evaluation_runtime: Union[FeatureFlagEvaluationRuntime, str]) -> list[str] +``` + +Return the keys of locally loaded flags that a runtime can evaluate. + +A flag set to `FeatureFlagEvaluationRuntime.ALL` suits either runtime, +so it is returned for `CLIENT` and for `SERVER`, and asking for +`ALL` returns every loaded flag. Use this to decide which flags to hand +to a browser when a backend serves flags to its own frontend. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`evaluation_runtime` | Union[FeatureFlagEvaluationRuntime, str] | The runtime to match, as a `FeatureFlagEvaluationRuntime` or its string value. | *required* + +**Returns:** + +Type | Description +---- | ----------- +list[str] | The matching flag keys, in the order local evaluation loaded them. +list[str] | Empty when no definitions are loaded. + +**Examples:** + +```python +from posthog import FeatureFlagEvaluationRuntime + +client_keys = posthog.get_feature_flag_keys_by_evaluation_runtime( + FeatureFlagEvaluationRuntime.CLIENT +) +``` + +
+Category + +Feature flags + +
+ +### `posthog.integrations.django.PosthogContextMiddleware` + +```python +PosthogContextMiddleware(get_response) +``` + +Middleware to automatically track Django requests. + +This middleware wraps all calls with a posthog context. It attempts to extract the following from the request: +- Session ID, (extracted from `X-POSTHOG-SESSION-ID`) +- Distinct ID, (extracted from `X-POSTHOG-DISTINCT-ID`, falling back to the authenticated request user ID) +- Authenticated user email as `email` +- Request URL as `$current_url` +- Request method as `$request_method` +- Request path as `$request_path` +- Forwarded IP address as `$ip` +- User agent as `$user_agent` + +Configure the integration with these Django settings: +- `POSTHOG_MW_CLIENT`: client used for automatic exception capture, or the global client. + This does not redirect events explicitly captured through other clients. +- `POSTHOG_MW_CAPTURE_EXCEPTIONS`: `False` enables context enrichment only; + `True` enables automatic exception capture; `None` inherits the effective client's + `enable_exception_autocapture`. When omitted, the legacy default is `True`. +- `POSTHOG_MW_REQUEST_FILTER`: a callback receiving the request. Returning `False` + disables both enrichment and automatic exception capture for that request. +- `POSTHOG_MW_EXTRA_PROPERTIES`: a callback returning additional event properties. +- `POSTHOG_MW_CAPTURE_TRACING_HEADERS`: read PostHog identity and session headers + as request-scoped analytics context (default: `True`). Explicit capture IDs and + event properties take precedence. These values are never authentication or + authorization. Authenticated Django user identity remains the fallback when no + usable header ID is available or header capture is disabled. +- `POSTHOG_MW_TRUST_TRACING_HEADERS`: deprecated alias for + `POSTHOG_MW_CAPTURE_TRACING_HEADERS`; the capture-named setting takes precedence. +- `POSTHOG_MW_PROPERTIES_MAP`: an optional callback transforming request properties. + +The old `POSTHOG_MW_EXTRA_TAGS` and `POSTHOG_MW_TAG_MAP` settings remain aliases; +the property-named settings take precedence when both are configured. + +Request properties are included on all events captured within the active context, +including when automatic exception capture is disabled. +See the context documentation for more information. The extracted distinct ID and session ID, +if found, are used to associate all events captured in the middleware context with the same distinct ID +and session as currently active on the frontend. See the documentation for `set_context_session` +and `identify_context` for more details. + +This middleware is hybrid-capable: it supports both WSGI (sync) and ASGI (async) Django applications. The middleware +detects at initialization whether the next middleware in the chain is async or sync, and adapts its behavior accordingly. +This ensures compatibility with both pure sync and pure async middleware chains, as well as mixed chains in ASGI mode. + +**Functions:** + +Name | Description +---- | ----------- +`aextract_properties` | Async version of extract_properties for use in async request handling. +`aextract_request_user` | Async version of extract_request_user for use in async request handling. +`aextract_tags` | Compatibility alias for `aextract_properties`. +`extract_properties` | Extract event properties and identity from a synchronous request. +`extract_request_user` | Extract user ID and email from request in sync context. +`extract_tags` | Compatibility alias for `extract_properties`. +`process_exception` | Process exceptions from views and downstream middleware. + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`async_capable` | | +`capture_exceptions` | Optional[bool] | +`capture_tracing_headers` | | +`client` | | +`extra_properties` | | +`extra_tags` | | Compatibility alias for `extra_properties`. +`get_response` | | +`properties_map` | | +`request_filter` | | +`sync_capable` | | +`tag_map` | | Compatibility alias for `properties_map`. +`trust_tracing_headers` | bool | Deprecated alias for `capture_tracing_headers`. + +Initialize the middleware with Django's next handler. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`get_response` | | The next middleware or view handler in Django's middleware chain. May be synchronous or asynchronous. | + +#### `posthog.integrations.django.PosthogContextMiddleware.aextract_properties` + +```python +aextract_properties(request) +``` + +Async version of extract_properties for use in async request handling. + +Uses await request.auser() instead of request.user to avoid +SynchronousOnlyOperation in async context. + +Follows Django's naming convention for async methods (auser, asave, etc.). + +#### `posthog.integrations.django.PosthogContextMiddleware.aextract_request_user` + +```python +aextract_request_user(request) +``` + +Async version of extract_request_user for use in async request handling. + +Uses await request.auser() instead of request.user to avoid +SynchronousOnlyOperation in async context. + +Follows Django's naming convention for async methods (auser, asave, etc.). + +#### `posthog.integrations.django.PosthogContextMiddleware.aextract_tags` + +```python +aextract_tags(request) +``` + +Compatibility alias for `aextract_properties`. + +#### `posthog.integrations.django.PosthogContextMiddleware.async_capable` + +```python +async_capable = True +``` + +#### `posthog.integrations.django.PosthogContextMiddleware.capture_exceptions` + +```python +capture_exceptions: Optional[bool] = capture_exceptions if (capture_exceptions is None or isinstance(capture_exceptions, bool)) else True +``` + +#### `posthog.integrations.django.PosthogContextMiddleware.capture_tracing_headers` + +```python +capture_tracing_headers = _resolve_capture_tracing_headers(capture_tracing_headers, trust_tracing_headers, canonical_name='POSTHOG_MW_CAPTURE_TRACING_HEADERS', deprecated_name='POSTHOG_MW_TRUST_TRACING_HEADERS') +``` + +#### `posthog.integrations.django.PosthogContextMiddleware.client` + +```python +client = cast('Optional[Client]', settings.POSTHOG_MW_CLIENT) +``` + +#### `posthog.integrations.django.PosthogContextMiddleware.extra_properties` + +```python +extra_properties = cast('Callable[[HttpRequest], Dict[str, Any]]', extra_properties) if callable(extra_properties) else None +``` + +#### `posthog.integrations.django.PosthogContextMiddleware.extra_tags` + +```python +extra_tags +``` + +Compatibility alias for `extra_properties`. + +#### `posthog.integrations.django.PosthogContextMiddleware.extract_properties` + +```python +extract_properties(request) +``` + +Extract event properties and identity from a synchronous request. + +#### `posthog.integrations.django.PosthogContextMiddleware.extract_request_user` + +```python +extract_request_user(request) +``` + +Extract user ID and email from request in sync context. + +#### `posthog.integrations.django.PosthogContextMiddleware.extract_tags` + +```python +extract_tags(request) +``` + +Compatibility alias for `extract_properties`. + +#### `posthog.integrations.django.PosthogContextMiddleware.get_response` + +```python +get_response = get_response +``` + +#### `posthog.integrations.django.PosthogContextMiddleware.process_exception` + +```python +process_exception(request, exception) +``` + +Process exceptions from views and downstream middleware. + +Django calls this WHILE still inside the context created by __call__, +so request tags have already been extracted and set. This method just +needs to capture the exception directly. + +Django converts view exceptions into responses before they propagate through +the middleware stack, so the context manager in __call__/__acall__ never sees them. + +Note: Django's process_exception is always synchronous, even for async views. + +#### `posthog.integrations.django.PosthogContextMiddleware.properties_map` + +```python +properties_map = cast('Callable[[Dict[str, Any]], Dict[str, Any]]', properties_map) if callable(properties_map) else None +``` + +#### `posthog.integrations.django.PosthogContextMiddleware.request_filter` + +```python +request_filter = cast('Optional[Callable[[HttpRequest], bool]]', settings.POSTHOG_MW_REQUEST_FILTER) +``` + +#### `posthog.integrations.django.PosthogContextMiddleware.sync_capable` + +```python +sync_capable = True +``` + +#### `posthog.integrations.django.PosthogContextMiddleware.tag_map` + +```python +tag_map +``` + +Compatibility alias for `properties_map`. + +#### `posthog.integrations.django.PosthogContextMiddleware.trust_tracing_headers` + +```python +trust_tracing_headers: bool +``` + +Deprecated alias for `capture_tracing_headers`. + +### `posthog.integrations.flask.PosthogFlaskIntegration` + +```python +PosthogFlaskIntegration(app: Optional[Flask] = None, *, client: Optional[Client] = None, capture_exceptions: Optional[bool] = None, request_filter: Optional[Callable[[Request], bool]] = None, extra_properties: Optional[Callable[[Request], Mapping[str, Any]]] = None, trust_tracing_headers: Optional[bool] = None, capture_tracing_headers: Optional[bool] = None) -> None +``` + +Add PostHog request context and exception tracking to a Flask app. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`app` | Optional[Flask] | An optional Flask application. If omitted, call `init_app` later (the Flask application-factory pattern). | None +`client` | Optional[Client] | Optional destination for automatically captured exceptions. The global client is used by default. Request context still enriches events captured through any client and does not redirect capture calls. | None +`capture_exceptions` | Optional[bool] | Capture exceptions that reach Flask's unhandled exception machinery. If omitted, inherit the effective client's `enable_exception_autocapture` setting. Pass `True` or `False` to override it. | None +`request_filter` | Optional[Callable[[Request], bool]] | Optional callback receiving Flask's request object. A false return value disables both context and exception capture for that request. | None +`extra_properties` | Optional[Callable[[Request], Mapping[str, Any]]] | Optional callback returning additional event properties. This is useful for application-specific metadata such as an authenticated user's role. Values should not contain secrets or request bodies. | None +`capture_tracing_headers` | Optional[bool] | Read PostHog distinct and session ID headers as request-scoped analytics context. Defaults to True. Explicit capture IDs and event properties take precedence. Never use these values for security. | None +`trust_tracing_headers` | Optional[bool] | Deprecated alias for `capture_tracing_headers`. The capture-named option takes precedence when both are supplied. | None + +The integration intentionally does not capture exceptions handled by an +application error handler, expected HTTP exceptions, request or response +bodies, query strings, cookies, authorization headers, or arbitrary headers. +Call `capture_exception` explicitly from a custom error handler if a +handled exception should be reported. + +**Functions:** + +Name | Description +---- | ----------- +`init_app` | Register the integration with a Flask application once. + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`capture_exceptions` | | +`capture_tracing_headers` | | +`client` | | +`extra_properties` | | +`request_filter` | | +`trust_tracing_headers` | bool | Deprecated alias for `capture_tracing_headers`. + +#### `posthog.integrations.flask.PosthogFlaskIntegration.capture_exceptions` + +```python +capture_exceptions = capture_exceptions +``` + +#### `posthog.integrations.flask.PosthogFlaskIntegration.capture_tracing_headers` + +```python +capture_tracing_headers = _resolve_capture_tracing_headers(capture_tracing_headers, trust_tracing_headers) +``` + +#### `posthog.integrations.flask.PosthogFlaskIntegration.client` + +```python +client = client +``` + +#### `posthog.integrations.flask.PosthogFlaskIntegration.extra_properties` + +```python +extra_properties = extra_properties +``` + +#### `posthog.integrations.flask.PosthogFlaskIntegration.init_app` + +```python +init_app(app: Flask) -> None +``` + +Register the integration with a Flask application once. + +#### `posthog.integrations.flask.PosthogFlaskIntegration.request_filter` + +```python +request_filter = request_filter +``` + +#### `posthog.integrations.flask.PosthogFlaskIntegration.trust_tracing_headers` + +```python +trust_tracing_headers: bool +``` + +Deprecated alias for `capture_tracing_headers`. + +### `posthog.integrations.asgi.PosthogASGIMiddleware` + +```python +PosthogASGIMiddleware(app: _ASGIApp, client: Optional[Client] = None, capture_exceptions: Optional[bool] = None, request_filter: Optional[_RequestFilter] = None, extra_properties: Optional[_ExtraProperties] = None, trust_tracing_headers: Optional[bool] = None, *, capture_tracing_headers: Optional[bool] = None) -> None +``` + +Add PostHog context and automatic exception capture to an ASGI app. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`app` | _ASGIApp | The downstream ASGI application. | *required* +`client` | Optional[Client] | Optional destination for automatically captured exceptions. The global client is used by default. Request context still enriches events captured through any client and does not redirect capture calls. | None +`capture_exceptions` | Optional[bool] | Capture exceptions escaping the downstream app. If omitted, inherit the effective client's `enable_exception_autocapture` setting. Pass `True` or `False` to override it. | None +`request_filter` | Optional[_RequestFilter] | Optional sync or async callback receiving the ASGI scope. Returning `False` bypasses all instrumentation for that scope. | None +`extra_properties` | Optional[_ExtraProperties] | Optional sync or async callback receiving the ASGI scope and returning additional event properties. | None +`capture_tracing_headers` | Optional[bool] | Read PostHog distinct and session ID headers as request-scoped analytics context. Defaults to True. Explicit capture IDs and event properties take precedence. Never use these values for security. | None +`trust_tracing_headers` | Optional[bool] | Deprecated alias for `capture_tracing_headers`. The capture-named option takes precedence when both are supplied. | None + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`app` | | +`capture_exceptions` | | +`capture_tracing_headers` | | +`client` | | +`extra_properties` | | +`request_filter` | | +`trust_tracing_headers` | bool | Deprecated alias for `capture_tracing_headers`. + +#### `posthog.integrations.asgi.PosthogASGIMiddleware.app` + +```python +app = app +``` + +#### `posthog.integrations.asgi.PosthogASGIMiddleware.capture_exceptions` + +```python +capture_exceptions = capture_exceptions +``` + +#### `posthog.integrations.asgi.PosthogASGIMiddleware.capture_tracing_headers` + +```python +capture_tracing_headers = _resolve_capture_tracing_headers(capture_tracing_headers, trust_tracing_headers) +``` + +#### `posthog.integrations.asgi.PosthogASGIMiddleware.client` + +```python +client = client +``` + +#### `posthog.integrations.asgi.PosthogASGIMiddleware.extra_properties` + +```python +extra_properties = extra_properties +``` + +#### `posthog.integrations.asgi.PosthogASGIMiddleware.request_filter` + +```python +request_filter = request_filter +``` + +#### `posthog.integrations.asgi.PosthogASGIMiddleware.trust_tracing_headers` + +```python +trust_tracing_headers: bool +``` + +Deprecated alias for `capture_tracing_headers`. + +### `posthog.integrations.celery.PosthogCeleryIntegration` + +```python +PosthogCeleryIntegration(client: Optional[Client] = None, capture_exceptions: bool = True, capture_task_lifecycle_events: bool = True, propagate_context: bool = True, task_filter: Optional[Callable[[Optional[str], dict[str, Any]], bool]] = None) +``` + +Celery integration that captures task lifecycle events and exceptions. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`client` | Optional[Client] | Optional `Client` instance. When provided, all events and exceptions are captured through this client rather than the global `posthog` module. Don't skip this if using a custom flag definition cache provider, and pass the custom `Client` instance here initialized with the custom provider so fork safety for that provider is handled correctly. | None +`capture_exceptions` | bool | Whether to capture task exceptions via `capture_exception` (default `True`). | True +`capture_task_lifecycle_events` | bool | Whether to emit lifecycle events of the task such as "started", "success", "failure" etc. (default `True`). | True +`propagate_context` | bool | Whether to propagate PostHog context (distinct ID, session ID, tags) from the producer to the worker via task headers (default `True`). | True +`task_filter` | Optional[Callable[[Optional[str], dict[str, Any]], bool]] | Optional callback `(task_name, task_properties) -> bool` expected to return `False` if a given task should not be tracked. | None + +**Functions:** + +Name | Description +---- | ----------- +`instrument` | Connect Celery signal handlers to capture task events and exceptions. +`shutdown` | Disconnect all signal handlers registered by `instrument()`, flush all pending events +`uninstrument` | Disconnect Celery signal handlers and unregister exit cleanup. + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`capture_exceptions` | | +`capture_task_lifecycle_events` | | +`client` | | +`propagate_context` | | +`task_filter` | | + +#### `posthog.integrations.celery.PosthogCeleryIntegration.capture_exceptions` + +```python +capture_exceptions = capture_exceptions +``` + +#### `posthog.integrations.celery.PosthogCeleryIntegration.capture_task_lifecycle_events` + +```python +capture_task_lifecycle_events = capture_task_lifecycle_events +``` + +#### `posthog.integrations.celery.PosthogCeleryIntegration.client` + +```python +client = client +``` + +#### `posthog.integrations.celery.PosthogCeleryIntegration.instrument` + +```python +instrument() -> None +``` + +Connect Celery signal handlers to capture task events and exceptions. +Call this after initializing the PostHog client and this integration. + +If Celery runs on a single host, reinstrumenting in worker children is +not strictly necessary because the PostHog client and this integration +are fork-safe. If Celery workers run across multiple hosts, each worker +process must initialize PostHog, this integration, and call +`instrument()`. Celery provides `worker_process_init` signal to help +with this. + +#### `posthog.integrations.celery.PosthogCeleryIntegration.propagate_context` + +```python +propagate_context = propagate_context +``` + +#### `posthog.integrations.celery.PosthogCeleryIntegration.shutdown` + +```python +shutdown() -> None +``` + +Disconnect all signal handlers registered by `instrument()`, flush all pending events +and cleanly shutdown the integration. + +`shutdown()` is also registered on `worker_process_shutdown` and `atexit` signals, +but there is no guarantee those will always be called, so we strongly recommend calling +it manually when the integration is no longer needed to avoid data loss. + +#### `posthog.integrations.celery.PosthogCeleryIntegration.task_filter` + +```python +task_filter = task_filter +``` + +#### `posthog.integrations.celery.PosthogCeleryIntegration.uninstrument` + +```python +uninstrument() -> None +``` + +Disconnect Celery signal handlers and unregister exit cleanup. + +Do not use directly, call `shutdown()` instead. + +### `posthog.request.set_socket_options` + +```python +set_socket_options(socket_options: Optional[SocketOptions]) -> None +``` + +Configure socket options for all SDK HTTP connections. + +Call this during initialization, before making API requests. Pass `None` +to reset to the default socket behavior. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`socket_options` | Optional[SocketOptions] | A list of `(level, option, value)` tuples accepted by urllib3/`socket.setsockopt()`, or `None` to reset defaults. | *required* + +
+Example + +from posthog import set_socket_options +set_socket_options([(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)]) + +
+ +### `posthog.request.enable_keep_alive` + +```python +enable_keep_alive() -> None +``` + +Enable TCP keepalive for SDK HTTP connections. + +This helps prevent idle pooled connections from being dropped by network +infrastructure. Call during initialization, before making API requests. + +### `posthog.request.disable_connection_reuse` + +```python +disable_connection_reuse() -> None +``` + +Disable HTTP connection reuse for SDK requests. + +Each request will create a fresh connection. This can avoid issues with +environments that terminate pooled connections, but adds per-request +overhead. Call during initialization, before making API requests. + +## AsyncClient + +Needs the `async` extra, `posthog[async]`. + +### `AsyncClient.alias` + +```python +alias(previous_id: ID_TYPES, distinct_id: Optional[str], timestamp: Optional[Union[datetime, str]] = None, uuid: Optional[str] = None, disable_geoip: Optional[bool] = None) -> Optional[str] +``` + +### `AsyncClient.capture` + +```python +capture(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] +``` + +Queue an event without blocking for network delivery. + +### `AsyncClient.capture_immediate` + +```python +capture_immediate(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] +``` + +Capture one event and wait until its delivery attempt completes. + +### `AsyncClient.capture_exception` + +```python +capture_exception(exception: Optional[ExceptionArg] = None, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] +``` + +Capture an exception. This method never raises, including in debug mode. + +### `AsyncClient.emails` + +```python +emails: AsyncEmails +``` + +Send transactional email from PostHog email templates, and read the +status of each email sent with `get`. + +Needs `secret_key`, a project secret API key with the `hog_flow:write` +scope, and `posthog[async]`. Sends go to `app_host`, or to the PostHog +Cloud app host of `host`. `email_request_timeout_seconds` and +`email_request_max_retries` configure each send. A disabled or shut-down +client sends nothing. See `posthog.emails` for setup. + +**Examples:** + +```python +await client.emails.send( + to="user@example.com", + template_id="0190f6d2-7c1e-7b3a-9f5e-1c2d3e4f5a6b", + distinct_id="user_123", +) +``` + +### `AsyncClient.flush` + +```python +flush(timeout_seconds: Optional[float] = 10) -> None +``` + +### `AsyncClient.evaluate_flags` + +```python +evaluate_flags(distinct_id: Optional[ID_TYPES] = None, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, disable_geoip: Optional[bool] = None, flag_keys: Optional[list[str]] = None, device_id: Optional[str] = None) -> FeatureFlagEvaluations +``` + +Evaluate flags remotely without blocking the event loop. + +The returned snapshot has synchronous in-memory accessors. Pass it to +`capture(flags=...)` to attach the exact values used for branching. +This client does not evaluate flags locally, so the snapshot's +`unresolved_flags` is always empty. + +### `AsyncClient.group_identify` + +```python +group_identify(group_type: str, group_key: str, properties: Optional[Dict[str, Any]] = None, timestamp: Optional[Union[datetime, str]] = None, uuid: Optional[Union[str, UUID]] = None, disable_geoip: Optional[bool] = None, distinct_id: Optional[ID_TYPES] = None) -> Optional[str] +``` + +### `AsyncClient.set` + +```python +set(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str] +``` + +### `AsyncClient.set_once` + +```python +set_once(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str] +``` + +### `AsyncClient(...)` + +```python +__init__(project_api_key: str, host: Optional[str] = None, debug: bool = False, max_queue_size: int = 10000, send: bool = True, on_error: bool = None, flush_at: int = 100, flush_interval: float = 5.0, gzip: bool = False, max_retries: int = 3, timeout: float = 15, thread: int = 1, disabled: bool = False, disable_geoip: bool = True, is_server: bool = True, historical_migration: bool = False, super_properties: Optional[dict[str, Any]] = None, before_send: Optional[dict[str, Any]] = None, log_captured_exceptions: bool = False, project_root: Optional[str] = None, capture_exception_code_variables: bool = False, code_variables_mask_patterns: bool = None, code_variables_ignore_patterns: bool = None, code_variables_mask_url_credentials: bool = None, code_variables_detect_secrets: bool = None, in_app_modules: Optional[list[str]] = None, capture_mode: Optional[Union[CaptureMode, str]] = None, capture_compression: Optional[Union[CaptureCompression, str]] = None, capture_trace_context: bool = False, secret_key: Optional[str] = None, personal_api_key: Optional[str] = None, feature_flags_request_timeout_seconds: float = 3, feature_flags_request_max_retries: int = 1, app_host: Optional[str] = None, email_request_timeout_seconds: float = 15, email_request_max_retries: int = 2) -> None +``` + +### `AsyncClient.shutdown` + +```python +shutdown() -> None +``` + +### `AsyncClient.join` + +```python +join() -> None +``` + +### `AsyncClient.get_remote_config_payload` + +```python +get_remote_config_payload(key: str) -> Optional[Any] +``` + +Fetch and decrypt a remote-config payload without blocking the loop. + +## Module functions + +### `posthog.new_context` + +```python +new_context(fresh: bool = False, capture_exceptions: Optional[bool] = None, client: Optional[Client] = None) +``` + +Create a new context scope that will be active for the duration of the with block. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`fresh` | bool | Whether to start with a fresh context (default: False) | False +`capture_exceptions` | Optional[bool] | Whether to capture exceptions raised within the context. If omitted, defaults to the relevant client's exception autocapture setting. | None +`client` | Optional[Client] | Optional Posthog client instance to use for this context (default: None) | None + +**Examples:** + +```python +from posthog import new_context, tag, capture +with new_context(): + tag("request_id", "123") + capture("event_name", properties={"property": "value"}) +``` + +
+Category + +Contexts + +
+ +### `posthog.scoped` + +```python +scoped(fresh = False, capture_exceptions: Optional[bool] = None) +``` + +Decorator that creates a new context for the function. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`fresh` | | Whether to start with a fresh context (default: False) | False +`capture_exceptions` | Optional[bool] | Whether to capture and track exceptions with posthog error tracking. If omitted, defaults to the global exception autocapture setting. | None + +**Examples:** + +```python +from posthog import scoped, tag, capture +@scoped() +def process_payment(payment_id): + tag("payment_id", payment_id) + capture("payment_started") +``` + +
+Category + +Contexts + +
+ +### `posthog.set_context_session` + +```python +set_context_session(session_id: str) +``` + +Set the session ID for the current context. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`session_id` | str | The session ID to associate with the current context and its children | *required* + +**Examples:** + +```python +from posthog import set_context_session +set_context_session("session_123") +``` + +
+Category + +Contexts + +
+ +### `posthog.set_context_device_id` + +```python +set_context_device_id(device_id: str) +``` + +Set the device ID for the current context, associating all feature flag requests +in this or child contexts with the given device ID. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`device_id` | str | The device ID to associate with the current context and its children | *required* + +**Examples:** + +```python +from posthog import set_context_device_id +set_context_device_id("device_123") +``` + +
+Category + +Contexts + +
+ +### `posthog.identify_context` + +```python +identify_context(distinct_id: str) +``` + +Identify the current context with a distinct ID. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | str | The distinct ID to associate with the current context and its children | *required* + +**Examples:** + +```python +from posthog import identify_context +identify_context("user_123") +``` + +
+Category + +Identification + +
+ +### `posthog.set_capture_exception_code_variables_context` + +```python +set_capture_exception_code_variables_context(enabled: bool) +``` + +Override code-variable capture for exceptions in the current context. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`enabled` | bool | Whether exceptions captured in this context should include local variable values from stack frames. | *required* + +
+Category + +Contexts + +
+ +### `posthog.set_code_variables_mask_patterns_context` + +```python +set_code_variables_mask_patterns_context(mask_patterns: list[str]) +``` + +Override code-variable mask patterns for exceptions in the current context. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`mask_patterns` | list[str] | Variable-name patterns whose values should be replaced with `***` when code variables are captured. | *required* + +
+Category + +Contexts + +
+ +### `posthog.set_code_variables_ignore_patterns_context` + +```python +set_code_variables_ignore_patterns_context(ignore_patterns: list[str]) +``` + +Override code-variable ignore patterns for exceptions in the current context. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`ignore_patterns` | list[str] | Variable-name patterns that should be omitted entirely when code variables are captured. | *required* + +
+Category + +Contexts + +
+ +### `posthog.set_code_variables_mask_url_credentials_context` + +```python +set_code_variables_mask_url_credentials_context(enabled: bool) +``` + +Whether to scrub credentials embedded in URLs/DSNs (e.g. user:pass@host) from +captured code variables for the current context. + +### `posthog.set_code_variables_detect_secrets_context` + +```python +set_code_variables_detect_secrets_context(enabled: bool) +``` + +Whether to apply entropy-based secret detection as a last-resort redaction of +high-entropy values (API keys, tokens, strong passwords) in captured code +variables for the current context. + +
+Category + +Contexts + +
+ +### `posthog.tag` + +```python +tag(name: str, value: Any) +``` + +Add a tag to the current context. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`name` | str | The tag key | *required* +`value` | Any | The tag value | *required* + +**Examples:** + +```python +from posthog import tag +tag("user_id", "123") +``` + +
+Category + +Contexts + +
+ +### `posthog.get_tags` + +```python +get_tags() -> Dict[str, Any] +``` + +Get all tags from the current context. + +**Returns:** + +Type | Description +---- | ----------- +Dict[str, Any] | Dict of all tags in the current context + +
+Category + +Contexts + +
+ +### `posthog.capture` + +```python +capture(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] +``` + +Capture anything a user does within your system. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`event` | str | The event name to specify the event | *required* +`**kwargs` | Unpack[OptionalCaptureArgs] | Optional arguments including: distinct_id: Unique identifier for the user properties: Dict of event properties timestamp: When the event occurred. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. uuid: Unique identifier for this event. If omitted, one is generated and returned. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. groups: Dict of group types and IDs flags: A FeatureFlagEvaluations snapshot from evaluate_flags(). The exact values from the snapshot are attached with no extra /flags request. send_feature_flags: Deprecated. Prefer flags=... from evaluate_flags(). When truthy, evaluates flags during capture and attaches them to the event. disable_geoip: Whether to disable GeoIP lookup | {} + +
+Details + +Capture allows you to capture anything a user does within your system, which you can later use in PostHog to find patterns in usage, work out which features to improve or where people are giving up. A capture call requires an event name to specify the event. We recommend using [verb] [noun], like `movie played` or `movie updated` to easily identify what your events mean later on. Capture takes a number of optional arguments, which are defined by the `OptionalCaptureArgs` type. + +
+ +**Examples:** + +```python +# Context and capture usage +from posthog import new_context, identify_context, tag_context, capture +# Enter a new context (e.g. a request/response cycle, an instance of a background job, etc) +with new_context(): + # Associate this context with some user, by distinct_id + identify_context('some user') + + # Capture an event, associated with the context-level distinct ID ('some user') + capture('movie started') + + # Capture an event associated with some other user (overriding the context-level distinct ID) + capture('movie joined', distinct_id='some-other-user') + + # Capture an event with some properties + capture('movie played', properties={'movie_id': '123', 'category': 'romcom'}) + + # Capture an event with some properties + capture('purchase', properties={'product_id': '123', 'category': 'romcom'}) + # Capture an event with some associated group + capture('purchase', groups={'company': 'id:5'}) + + # Adding a tag to the current context will cause it to appear on all subsequent events + tag_context('some-tag', 'some-value') + + capture('another-event') # Will be captured with `'some-tag': 'some-value'` in the properties dict +``` +```python +# Set event properties +from posthog import capture +capture( + "user_signed_up", + distinct_id="distinct_id_of_the_user", + properties={ + "login_type": "email", + "is_free_trial": "true" + } +) +``` + +### `posthog.capture_ai` + +```python +capture_ai(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] +``` + +Capture an AI event on the dedicated AI capture endpoint. + +Beta: the signature is stable; operational limits (per-event size cap, +batching, endpoint) may change without notice. + +Takes the same arguments and returns the same value as `capture()`: the +event UUID, or None when the event was not admitted (disabled client, or +dropped by `before_send`). The event is delivered on an isolated queue +with its own consumer pool and a higher per-event size cap, posting to +the dedicated AI ingestion endpoint. The payload is sent as given — no +redaction or truncation is applied here. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`event` | str | The event name, normally one of the `$ai_*` event names. | *required* +`**kwargs` | Unpack[OptionalCaptureArgs] | Same optional arguments as `capture()`. | {} + +**Examples:** + +```python +from posthog import capture_ai + +uuid = capture_ai( + "$ai_generation", + distinct_id="user_123", + properties={"$ai_model": "gpt-5"}, +) +``` + +### `posthog.set` + +```python +set(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str] +``` + +Set properties on a user record. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`**kwargs` | Unpack[OptionalSetArgs] | Optional arguments including: distinct_id: Unique identifier for the user. Falls back to the context distinct ID; if none exists, this call does nothing. properties: Dict of person properties to set. timestamp: When the properties were set. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. uuid: Unique identifier for this operation. If omitted, one is generated and returned. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. disable_geoip: Whether to disable GeoIP lookup. | {} + +
+Details + +This will overwrite previous people property values. Generally operates similar to `capture`, with distinct_id being an optional argument, defaulting to the current context's distinct ID. If there is no context-level distinct ID, and no override distinct_id is passed, this function will do nothing. Context tags are folded into $set properties, so tagging the current context and then calling `set` will cause those tags to be set on the user (unlike capture, which causes them to just be set on the event). + +
+ +**Examples:** + +```python +# Set person properties +from posthog import set +set(distinct_id='distinct_id', properties={'name': 'Max Hedgehog'}) +``` + +### `posthog.set_once` + +```python +set_once(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str] +``` + +Set properties on a user record, only if they do not yet exist. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`**kwargs` | Unpack[OptionalSetArgs] | Optional arguments including: distinct_id: Unique identifier for the user. Falls back to the context distinct ID; if none exists, this call does nothing. properties: Dict of person properties to set only once. timestamp: When the properties were set. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. uuid: Unique identifier for this operation. If omitted, one is generated and returned. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. disable_geoip: Whether to disable GeoIP lookup. | {} + +
+Details + +This will not overwrite previous people property values, unlike `set`. Otherwise, operates in an identical manner to `set`. + +
+ +**Examples:** + +```python +# Set property once +from posthog import set_once +set_once(distinct_id='distinct_id', properties={'initial_url': '/blog'}) + +``` + +### `posthog.group_identify` + +```python +group_identify(group_type: str, group_key: str, properties: Optional[Dict[str, Any]] = None, timestamp: Optional[Union[datetime.datetime, str]] = None, uuid: Optional[str] = None, disable_geoip: Optional[bool] = None, distinct_id: Optional[ID_TYPES] = None) -> Optional[str] +``` + +Set properties on a group. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`group_type` | str | Type of your group. Required - the call is dropped with a warning if it is missing or empty. | *required* +`group_key` | str | Unique identifier of the group. Required - the call is dropped with a warning if it is missing or empty. | *required* +`properties` | Optional[Dict[str, Any]] | Properties to set on the group | None +`timestamp` | Optional[Union[datetime, str]] | Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | None +`uuid` | Optional[str] | Optional UUID for the event | None +`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup | None +`distinct_id` | Optional[ID_TYPES] | Optional distinct ID of the user performing the action | None + +**Examples:** + +```python +# Group identify +from posthog import group_identify +group_identify('company', 'company_id_in_your_db', { + 'name': 'Awesome Inc.', + 'employees': 11 +}) +``` + +### `posthog.alias` + +```python +alias(previous_id: ID_TYPES, distinct_id: str, timestamp: Optional[Union[datetime.datetime, str]] = None, uuid: Optional[str] = None, disable_geoip: Optional[bool] = None) -> Optional[str] +``` + +Associate user behaviour before and after they e.g. register, login, or perform some other identifying action. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`previous_id` | ID_TYPES | The unique ID of the user before | *required* +`distinct_id` | str | The current unique id | *required* +`timestamp` | Optional[Union[datetime, str]] | Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | None +`uuid` | Optional[str] | Optional UUID for the event | None +`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup | None + +
+Details + +To marry up whatever a user does before they sign up or log in with what they do after you need to make an alias call. This will allow you to answer questions like "Which marketing channels leads to users churning after a month?" or "What do users do on our website before signing up?". Particularly useful for associating user behaviour before and after they e.g. register, login, or perform some other identifying action. + +
+ +**Examples:** + +```python +# Alias user +from posthog import alias +alias(previous_id='distinct_id', distinct_id='alias_id') +``` + +### `posthog.capture_exception` + +```python +capture_exception(exception: Optional[ExceptionArg] = None, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] +``` + +Capture exceptions that happen in your code. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`exception` | Optional[ExceptionArg] | The exception to capture. If not provided, the current exception is captured via `sys.exc_info()` | None +`**kwargs` | Unpack[OptionalCaptureArgs] | Optional capture arguments including distinct_id, properties, timestamp, uuid, groups, flags, send_feature_flags, and disable_geoip. Overriding reserved exception properties through `properties` is deprecated and will stop working in the next major version. | {} + +
+Details + +Capture exception is idempotent - if it is called twice with the same exception instance, only a occurrence will be tracked in posthog. This is because, generally, contexts will cause exceptions to be captured automatically. However, to ensure you track an exception, if you catch and do not re-raise it, capturing it manually is recommended, unless you are certain it will have crossed a context boundary (e.g. by existing a `with posthog.new_context():` block already). If the passed exception was raised and caught, the captured stack trace will consist of every frame between where the exception was raised and the point at which it is captured (the "traceback"). If the passed exception was never raised, e.g. if you call `posthog.capture_exception(ValueError("Some Error"))`, the stack trace captured will be the full stack trace at the moment the exception was captured. Note that heavy use of contexts will lead to truncated stack traces, as the exception will be captured by the context entered most recently, which may not be the point you catch the exception for the final time in your code. It's recommended to use contexts sparingly, for this reason. `capture_exception` takes the same set of optional arguments as `capture`. + +
+ +**Examples:** + +```python +# Capture exception +from posthog import capture_exception +try: + risky_operation() +except Exception as e: + capture_exception(e) +``` + +### `posthog.feature_enabled` + +```python +feature_enabled(key: str, distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[bool] +``` + +Use feature flags to enable or disable features for users. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The feature flag key | *required* +`distinct_id` | ID_TYPES | The user's distinct ID | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | Groups mapping | None +`person_properties` | Optional[Dict[str, Any]] | Person properties | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties | None +`only_evaluate_locally` | bool | Whether to evaluate only locally | False +`send_feature_flag_events` | bool | Whether to send feature flag events | True +`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup | None +`device_id` | Optional[str] | Optional device ID override for experience-continuity flags | None + +
+Details + +You can call `posthog.load_feature_flags()` before to make sure you're not doing unexpected requests. + +
+ +**Examples:** + +```python +# Boolean feature flag +from posthog import feature_enabled, get_feature_flag_payload +is_my_flag_enabled = feature_enabled('flag-key', 'distinct_id_of_your_user') +if is_my_flag_enabled: + matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user') +``` + +### `posthog.get_feature_flag` + +```python +get_feature_flag(key: str, distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[FlagValue] +``` + +Get feature flag variant for users. Used with experiments. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The feature flag key | *required* +`distinct_id` | ID_TYPES | The user's distinct ID | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | Groups mapping from group type to group key | None +`person_properties` | Optional[Dict[str, Any]] | Person properties | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties in format { group_type_name: { group_properties } } | None +`only_evaluate_locally` | bool | Whether to evaluate only locally | False +`send_feature_flag_events` | bool | Whether to send feature flag events | True +`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup | None +`device_id` | Optional[str] | Optional device ID override for experience-continuity flags | None + +
+Details + +`groups` are a mapping from group type to group key. So, if you have a group type of "organization" and a group key of "5", you would pass groups={"organization": "5"}. `group_properties` take the format: { group_type_name: { group_properties } }. So, for example, if you have the group type "organization" and the group key "5", with the properties name, and employee count, you'll send these as: group_properties={"organization": {"name": "PostHog", "employees": 11}}. + +
+ +**Examples:** + +```python +# Multivariate feature flag +from posthog import get_feature_flag, get_feature_flag_payload +enabled_variant = get_feature_flag('flag-key', 'distinct_id_of_your_user') +if enabled_variant == 'variant-key': + matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user') +``` + +### `posthog.get_all_flags` + +```python +get_all_flags(distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None, flag_keys_to_evaluate: Optional[list[str]] = None) -> Optional[dict[str, FlagValue]] +``` + +Get all flags for a given user. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | ID_TYPES | The user's distinct ID | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | Groups mapping | None +`person_properties` | Optional[Dict[str, Any]] | Person properties | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties | None +`only_evaluate_locally` | bool | Whether to evaluate only locally | False +`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup | None +`device_id` | Optional[str] | Optional device ID override for experience-continuity flags | None +`flag_keys_to_evaluate` | Optional[list[str]] | Optional list of flag keys to evaluate (evaluates all if None) | None + +
+Details + +Flags are key-value pairs where the key is the flag key and the value is the flag variant, or True, or False. + +
+ +**Examples:** + +```python +# All flags for user +from posthog import get_all_flags +get_all_flags('distinct_id_of_your_user') +``` + +### `posthog.get_feature_flag_result` + +```python +get_feature_flag_result(key: str, distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[FeatureFlagResult] +``` + +Get a FeatureFlagResult object which contains the flag result and payload. + +This method evaluates a feature flag and returns a FeatureFlagResult object containing: +- enabled: Whether the flag is enabled +- variant: The variant value if the flag has variants +- payload: The payload associated with the flag (automatically deserialized from JSON) +- key: The flag key +- reason: Why the flag was enabled/disabled + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The feature flag key. | *required* +`distinct_id` | ID_TYPES | The user's distinct ID. | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | Mapping of group type to group key. | None +`person_properties` | Optional[Dict[str, Any]] | Person properties to use for evaluation. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties keyed by group type. | None +`only_evaluate_locally` | bool | Whether to evaluate only locally. | False +`send_feature_flag_events` | bool | Whether to send a $feature_flag_called event. | True +`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup. | None +`device_id` | Optional[str] | Optional device ID override for experience-continuity flags. | None + +Example: +```python +result = posthog.get_feature_flag_result('beta-feature', 'distinct_id') +if result and result.enabled: + # Use the variant and payload + print(f"Variant: {result.variant}") + print(f"Payload: {result.payload}") +``` + +### `posthog.get_feature_flag_payload` + +```python +get_feature_flag_payload(key: str, distinct_id: ID_TYPES, match_value: Optional[FlagValue] = None, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[object] +``` + +Get the payload associated with a feature flag value. + +Deprecated for new code. Prefer `evaluate_flags()` and +`flags.get_flag_payload(key)` so flag evaluation happens once per request. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The feature flag key. | *required* +`distinct_id` | ID_TYPES | The user's distinct ID. | *required* +`match_value` | Optional[FlagValue] | Optional flag value to use when selecting a payload. | None +`groups` | Optional[Mapping[str, Union[str, int]]] | Mapping of group type to group key. | None +`person_properties` | Optional[Dict[str, Any]] | Person properties to use for evaluation. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties keyed by group type. | None +`only_evaluate_locally` | bool | Whether to evaluate only locally. | False +`send_feature_flag_events` | bool | Whether to send a $feature_flag_called event. | True +`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup. | None +`device_id` | Optional[str] | Optional device ID override for experience-continuity flags. | None + +**Returns:** + +Type | Description +---- | ----------- +Optional[object] | The payload associated with the matched feature flag value, or None. +Optional[object] | This function returns the payload only, not the FeatureFlagResult wrapper +Optional[object] | used internally to compute it. + +### `posthog.get_remote_config_payload` + +```python +get_remote_config_payload(key: str) +``` + +Get the payload for a remote config feature flag. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The key of the feature flag | *required* + +**Returns:** + +Type | Description +---- | ----------- + | The payload associated with the feature flag. If payload is encrypted, the return value will be decrypted + +
+Note + +Requires secret_key to be set for authentication + +
+ +### `posthog.get_all_flags_and_payloads` + +```python +get_all_flags_and_payloads(distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None, flag_keys_to_evaluate: Optional[list[str]] = None) -> FlagsAndPayloads +``` + +Get all feature flag values and payloads for a user. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | ID_TYPES | The user's distinct ID. | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | Mapping of group type to group key. | None +`person_properties` | Optional[Dict[str, Any]] | Person properties to use for evaluation. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties keyed by group type. | None +`only_evaluate_locally` | bool | Whether to evaluate only locally. | False +`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup. | None +`device_id` | Optional[str] | Optional device ID override for experience-continuity flags. | None +`flag_keys_to_evaluate` | Optional[list[str]] | Optional list of flag keys to evaluate. Evaluates all flags when omitted. | None + +**Returns:** + +Type | Description +---- | ----------- +FlagsAndPayloads | A dict with `featureFlags` and `featureFlagPayloads` entries. + +### `posthog.evaluate_flags` + +```python +evaluate_flags(distinct_id: Optional[ID_TYPES] = None, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, flag_keys: Optional[list[str]] = None, device_id: Optional[str] = None) -> FeatureFlagEvaluations +``` + +Evaluate all feature flags for a user in a single call and return a +`FeatureFlagEvaluations` snapshot. Branch on `.is_enabled()` / +`.get_flag()` and pass the same snapshot to `capture()` via the +`flags` option so events carry the exact flag values the code branched on. + +Prefer this over repeated `get_feature_flag()` calls and over +`capture(send_feature_flags=True)` — it consolidates flag evaluation into +a single `/flags` request per incoming request. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | Optional[ID_TYPES] | The user's distinct ID. If `None`, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot. | None +`groups` | Optional[Mapping[str, Union[str, int]]] | Mapping of group type to group key. | None +`person_properties` | Optional[Dict[str, Any]] | Person properties to use for evaluation. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties keyed by group type. | None +`only_evaluate_locally` | bool | If `True`, never fall back to remote evaluation and omit flags that cannot be evaluated locally. The snapshot's `unresolved_flags` lists them with a reason. | False +`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup. | None +`flag_keys` | Optional[list[str]] | Optional list that scopes local evaluation, the underlying `/flags` request, and the returned snapshot. When omitted or `None`, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per `evaluate_flags` call unless `only_evaluate_locally` is `True`. If the server also does not know the key, it is omitted from the snapshot. | None +`device_id` | Optional[str] | Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes. | None + +**Examples:** + +```python +from posthog import evaluate_flags, capture +flags = evaluate_flags("user_123", person_properties={"plan": "enterprise"}) +if flags.is_enabled("new-dashboard"): + render_new_dashboard() +capture("page_viewed", distinct_id="user_123", flags=flags) +``` + +### `posthog.feature_flag_definitions` + +```python +feature_flag_definitions() +``` + +Returns loaded feature flags. + +
+Details + +Returns loaded feature flags, if any. Helpful for debugging what flag information you have loaded. + +
+ +**Examples:** + +```python +from posthog import feature_flag_definitions +definitions = feature_flag_definitions() +``` + +### `posthog.get_feature_flag_evaluation_runtime` + +```python +get_feature_flag_evaluation_runtime(key: str) -> Optional[FeatureFlagEvaluationRuntime] +``` + +Return where a locally loaded feature flag is meant to be evaluated. + +
+Details + +Reads the `evaluation_runtime` each flag definition carries, so no extra +request is made. Returns `None` when local evaluation has not loaded a +definition for this key. A definition that carries no runtime reports +`FeatureFlagEvaluationRuntime.ALL`, the default PostHog applies. + +
+ +**Examples:** + +```python +from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_evaluation_runtime +runtime = get_feature_flag_evaluation_runtime("my-flag") +``` + +
+Category + +Feature flags + +
+ +### `posthog.get_feature_flag_keys_by_evaluation_runtime` + +```python +get_feature_flag_keys_by_evaluation_runtime(evaluation_runtime: Union[FeatureFlagEvaluationRuntime, str]) -> list[str] +``` + +Return the keys of locally loaded flags that a runtime can evaluate. + +
+Details + +A flag set to `FeatureFlagEvaluationRuntime.ALL` suits either runtime, so +it is returned for `CLIENT` and for `SERVER`. Use this to decide which +flags to hand to a browser when a backend serves flags to its own +frontend. + +
+ +**Examples:** + +```python +from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_keys_by_evaluation_runtime +client_keys = get_feature_flag_keys_by_evaluation_runtime(FeatureFlagEvaluationRuntime.CLIENT) +``` + +
+Category + +Feature flags + +
+ +### `posthog.load_feature_flags` + +```python +load_feature_flags() +``` + +Load feature flag definitions from PostHog. + +**Examples:** + +```python +from posthog import load_feature_flags +load_feature_flags() +``` + +### `posthog.flush` + +```python +flush(timeout_seconds: Optional[float] = 10) -> None +``` + +Attempt immediate delivery of queued events and ended spans, keeping the global client reusable. + +Call `flush()` before a short-lived request or serverless invocation returns +when using the shared module-level client. Use `shutdown()` only for final +process cleanup or a client that will not be reused. Flush does not stop the +background workers or feature flag poller, and does not end open spans. + +Returning does not guarantee server receipt: pending work can remain after a +timeout or span export failure, and failed events may be dropped by consumers. +Calls made directly from SDK callbacks such as `on_error` are deferred +rather than blocking the worker that invoked the callback. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`timeout_seconds` | Optional[float] | Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass `None` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently, waiting up to this many seconds for an in-flight flush before starting a fresh drain budget. A lock wait timeout leaves the in-flight flush to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. The last span request is not cut short and is bounded by the client's `timeout` in seconds, so this is not a strict wall-clock limit for the whole call. With `None`, a retriable span failure is left for a later flush rather than retried within this call. | 10 + +**Examples:** + +```python +import posthog +posthog.capture('event_name', distinct_id='user_id') +# Before a serverless invocation returns; keep the global client open. +posthog.flush(timeout_seconds=10) +``` + +### `posthog.join` + +```python +join() -> None +``` + +Attempt to process queued events and stop the client's background workers. Use `shutdown()` directly in most cases. + +Failed or undrainable events may be dropped and reported through logging or +`on_error`; returning does not guarantee server receipt. Lifecycle cleanup +is attempted once, and cleanup failures are logged without retry. + +**Examples:** + +```python +from posthog import join +join() +``` + +### `posthog.start_span` + +```python +start_span(name: str, *, kind: Optional[str] = None, attributes: Optional[Mapping[str, Any]] = None, parent: Union[Span, str, None] = None, tracestate: Optional[str] = None, start_time: Union[datetime.datetime, float, None] = None) -> Span +``` + +Start a span for distributed tracing. Alpha. + +Returns a span handle. Use it as a context manager to make it the active +span for the block and end it on exit (recording a raised exception on the +way out); or call `end()` yourself for a span that cannot wrap a block. +Spans started inside the block nest under it automatically. Always returns +a usable handle, even when tracing is off, so calling code never branches. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`name` | str | A low-cardinality operation name, e.g. `GET /users/:id`. Variable values belong in attributes, not the name. | *required* +`kind` | Optional[str] | `internal` (default), `server`, `client`, `producer` or `consumer`. | None +`attributes` | Optional[Mapping[str, Any]] | Initial attributes. | None +`parent` | Union[Span, str, None] | A span handle, or an inbound W3C `traceparent` header value to continue a remote trace. Defaults to the active span. | None +`tracestate` | Optional[str] | The inbound `tracestate` header accompanying a `traceparent` string `parent`; preserved and propagated. | None +`start_time` | Union[datetime, float, None] | A `datetime` or epoch seconds, to backdate the span. | None + +**Examples:** + +```python +import posthog +posthog.traces = {"service_name": "checkout-api"} + +with posthog.start_span("POST /checkout", parent=request.headers.get("traceparent")) as span: + span.set_attribute("plan", user.plan) + with posthog.start_span("db.query", kind="client"): + ... + outgoing_headers = {"traceparent": span.traceparent()} +``` + +### `posthog.get_active_span` + +```python +get_active_span() -> Optional[Span] +``` + +The span that is active in the current context, or `None`. Alpha. + +Only entering a span (`with posthog.start_span(...) as span:`) makes it +active; a span started manually is not. Use it to propagate the trace to +the next service: `span.traceparent()` is the header value. + +**Examples:** + +```python +span = posthog.get_active_span() +if span is not None: + headers["traceparent"] = span.traceparent() +``` + +### `posthog.shutdown` + +```python +shutdown() -> None +``` + +Attempt final delivery and permanently shut down the global client. + +Call this for final process cleanup, not at the end of each request or +serverless invocation that shares the module-level client. Use `flush()` +before returning from those invocations instead. Shutdown closes capture +lanes, stops background workers and the feature flag poller, and tears down +integrations. The global client is not recreated automatically after shutdown. + +This normally blocks until queued events have been attempted and cleanup +finishes. Failed or undrainable events may be dropped and reported through +logging or `on_error`; returning does not guarantee server receipt. Event +draining has no timeout. Queued spans get a final flush with up to 30 seconds +waiting for an in-flight flush, then a fresh 30-second drain budget; the last +HTTP request is not cut short. Unsent queued spans and still-open spans are +discarded with a warning. +Lifecycle cleanup is attempted once, and cleanup failures are logged without +retry. Calls made directly from SDK callbacks such as `on_error` are deferred +to avoid deadlocking the worker. If blocking completion is required, signal an application-owned +thread, return from the callback, and call `shutdown()` from that thread. +Do not wait inside a callback for another thread or task calling a lifecycle +method. + +**Examples:** + +```python +from posthog import shutdown +shutdown() +``` + +### `posthog.setup` + +```python +setup() -> Client +``` + +Create or return the global PostHog client configured by module settings. + +Most applications should either instantiate `Posthog` directly or set +`posthog.api_key`/other module settings before calling top-level helpers. +`setup()` is called automatically by global APIs such as `capture()`. + +**Returns:** + +Type | Description +---- | ----------- +Client | The global `Client` instance. If both `api_key` and +Client | `project_api_key` are missing or blank, the client is disabled and +Client | module-level calls become no-ops. + +## Types + +### `OptionalCaptureArgs` + +Bases: TypedDict + +Optional arguments for the capture method. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | | Unique identifier for the person associated with this event. If not set, the context distinct_id is used, if available, otherwise a UUID is generated, and the event is marked as personless. Setting context-level distinct_id's is recommended. | +`properties` | | Dictionary of properties to track with the event | +`timestamp` | | When the event occurred (defaults to current time). UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | +`uuid` | | Unique identifier for this specific event. If not provided, one is generated. The event UUID is returned, so you can correlate it with actions in your app (like showing users an error ID if you capture an exception). If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | +`groups` | | Group identifiers to associate with this event (format: {group_type: group_key}) | +`flags` | | A `FeatureFlagEvaluations` snapshot from `evaluate_flags()`. The exact flag values from the snapshot are attached to the event with no additional network call — prefer this over `send_feature_flags`. | +`send_feature_flags` | | Deprecated — prefer `flags` with a `FeatureFlagEvaluations` snapshot. Whether to include currently active feature flags in the event properties. Can be a boolean or a SendFeatureFlagsOptions object. Defaults to False. Fires a hidden `/flags` request on capture and may return different values than the ones the code branched on. | +`disable_geoip` | | Whether to disable GeoIP lookup for this event. Defaults to False. | + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`disable_geoip` | NotRequired[Optional[bool]] | +`distinct_id` | NotRequired[Optional[ID_TYPES]] | +`flags` | NotRequired[Optional[FeatureFlagEvaluations]] | +`groups` | NotRequired[Optional[Dict[str, str]]] | +`properties` | NotRequired[Optional[Dict[str, Any]]] | +`send_feature_flags` | NotRequired[Optional[Union[bool, SendFeatureFlagsOptions]]] | +`timestamp` | NotRequired[Optional[Union[datetime, str]]] | +`uuid` | NotRequired[Optional[Union[str, UUID]]] | + +#### `posthog.args.OptionalCaptureArgs.disable_geoip` + +```python +disable_geoip: NotRequired[Optional[bool]] +``` + +#### `posthog.args.OptionalCaptureArgs.distinct_id` + +```python +distinct_id: NotRequired[Optional[ID_TYPES]] +``` + +#### `posthog.args.OptionalCaptureArgs.flags` + +```python +flags: NotRequired[Optional[FeatureFlagEvaluations]] +``` + +#### `posthog.args.OptionalCaptureArgs.groups` + +```python +groups: NotRequired[Optional[Dict[str, str]]] +``` + +#### `posthog.args.OptionalCaptureArgs.properties` + +```python +properties: NotRequired[Optional[Dict[str, Any]]] +``` + +#### `posthog.args.OptionalCaptureArgs.send_feature_flags` + +```python +send_feature_flags: NotRequired[Optional[Union[bool, SendFeatureFlagsOptions]]] +``` + +#### `posthog.args.OptionalCaptureArgs.timestamp` + +```python +timestamp: NotRequired[Optional[Union[datetime, str]]] +``` + +#### `posthog.args.OptionalCaptureArgs.uuid` + +```python +uuid: NotRequired[Optional[Union[str, UUID]]] +``` + +### `FeatureFlagEvaluations` + +```python +FeatureFlagEvaluations(host: _FeatureFlagEvaluationsHost, distinct_id: str, flags: Dict[str, _EvaluatedFlagRecord], groups: Optional[Mapping[str, Union[str, int]]] = None, disable_geoip: Optional[bool] = None, request_id: Optional[str] = None, evaluated_at: Optional[int] = None, errors_while_computing: bool = False, quota_limited: bool = False, minimal_flag_called_events: bool = False, accessed: Optional[Set[str]] = None, unresolved_flags: Optional[Mapping[str, UnresolvedFlagReason]] = None) -> None +``` + +A point-in-time snapshot of feature flag evaluations for a single distinct_id. + +Returned by `Client.evaluate_flags` — branch on `is_enabled` / +`get_flag` and pass the same object to `Client.capture` via the +`flags` option so the captured event carries the exact flag values the code +branched on. + +Example:: + + flags = posthog.evaluate_flags(distinct_id, person_properties={"plan": "enterprise"}) + if flags.is_enabled("new-dashboard"): + render_new_dashboard() + posthog.capture("page_viewed", distinct_id=distinct_id, flags=flags) + +To narrow the set of flags that get attached to a captured event, use the in-memory +helpers `only` and `only_accessed`. To narrow the set of flags requested +from the server in the first place, pass `flag_keys` to `Client.evaluate_flags`. + +**Functions:** + +Name | Description +---- | ----------- +`get_flag` | Return the flag value. Fires `$feature_flag_called` on first access. +`get_flag_payload` | Return the payload associated with a flag. +`is_enabled` | Return whether the flag is enabled. Fires `$feature_flag_called` on the +`only` | Return a filtered copy containing only flags with the given keys. Keys that +`only_accessed` | Return a filtered copy containing only flags accessed via `is_enabled` + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`keys` | List[str] | Return the flag keys that are part of this evaluation. +`unresolved_flags` | Mapping[str, UnresolvedFlagReason] | Return a read-only mapping of flag key to the reason local evaluation + +Internal — instances are created by the SDK via `Client.evaluate_flags()`. + +#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.get_flag` + +```python +get_flag(key: str, *, send_feature_flag_events: bool = True) -> Optional[FlagValue] +``` + +Return the flag value. Fires `$feature_flag_called` on first access. + +Returns the variant string for multivariate flags, `True` for enabled flags +without a variant, `False` for disabled flags, and `None` for flags that +were not returned by the evaluation. + +Pass `send_feature_flag_events=False` to skip the event for this read, as +with `is_enabled`. + +#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.get_flag_payload` + +```python +get_flag_payload(key: str) -> Optional[Any] +``` + +Return the payload associated with a flag. + +Does not count as an access for `only_accessed` and does not fire any event. + +#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.is_enabled` + +```python +is_enabled(key: str, default_value: bool = False, *, send_feature_flag_events: bool = True) -> bool +``` + +Return whether the flag is enabled. Fires `$feature_flag_called` on the +first access per (distinct_id, flag, value) tuple, deduped via the SDK's cache. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The feature flag key. | *required* +`default_value` | bool | Returned when the flag has no value in this evaluation — the key was not part of the evaluated set, or the evaluation itself came back empty (failed `/flags` request, quota limit, or no resolvable `distinct_id`). Defaults to `False`. | False +`send_feature_flag_events` | bool | Whether this read sends `$feature_flag_called`. When `False`, no event is sent and the dedupe cache is left alone, but the key still counts as accessed for `only_accessed`. | True + +A flag that has a value always wins over `default_value`, including a +disabled flag, which returns `False` even when `default_value=True`. + +#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.keys` + +```python +keys: List[str] +``` + +Return the flag keys that are part of this evaluation. + +#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.only` + +```python +only(keys: List[str]) -> FeatureFlagEvaluations +``` + +Return a filtered copy containing only flags with the given keys. Keys that +are not present in the evaluation are dropped and logged as a warning. + +#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.only_accessed` + +```python +only_accessed() -> FeatureFlagEvaluations +``` + +Return a filtered copy containing only flags accessed via `is_enabled` +or `get_flag` before this call. + +Order-dependent: if nothing has been accessed yet, the returned snapshot is +empty. The method honors its name — pre-access if you want a populated result. + +#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.unresolved_flags` + +```python +unresolved_flags: Mapping[str, UnresolvedFlagReason] +``` + +Return a read-only mapping of flag key to the reason local evaluation +left it without a value. + +A flag is listed when it has a loaded local definition, was in the requested +scope, was inconclusive locally, and no remote value filled the gap. Listed +flags are not part of `keys`, and reading one reports the +`local_evaluation_inconclusive` error instead of `flag_missing`. Reading +this mapping does not count as an access and does not fire any event. +Snapshots returned by `only` and `only_accessed`, and by +`AsyncClient.evaluate_flags()`, which evaluates only remotely, carry no +entries. + +### `FeatureFlagResult` + +```python +FeatureFlagResult(key: str, enabled: bool, variant: Optional[str], payload: Optional[Any], reason: Optional[str]) -> None +``` + +The result of calling a feature flag which includes the flag result, variant, and payload. + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`key` | str | The unique identifier of the feature flag. +`enabled` | bool | Whether the feature flag is enabled for the current context. +`variant` | Optional[str] | The variant value if the flag is enabled and has variants, None otherwise. +`payload` | Optional[Any] | Additional data associated with the feature flag, if any. +`reason` | Optional[str] | A description of why the flag was enabled or disabled, if available. + +**Functions:** + +Name | Description +---- | ----------- +`from_flag_details` | Create a FeatureFlagResult from a FeatureFlag object. +`from_value_and_payload` | Creates a FeatureFlagResult from a flag value and payload. +`get_value` | Returns the value of the flag. This is the variant if it exists, otherwise the enabled value. + +#### `posthog.types.FeatureFlagResult.enabled` + +```python +enabled: bool +``` + +#### `posthog.types.FeatureFlagResult.from_flag_details` + +```python +from_flag_details(details: Union[FeatureFlag, None], override_match_value: Optional[FlagValue] = None) -> FeatureFlagResult | None +``` + +Create a FeatureFlagResult from a FeatureFlag object. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`details` | Union[FeatureFlag, None] | The FeatureFlag object to convert. | *required* +`override_match_value` | Optional[FlagValue] | If provided, this value will be used to populate the enabled and variant fields instead of the values from the FeatureFlag. | None + +**Returns:** + +Type | Description +---- | ----------- +FeatureFlagResult \| None | FeatureFlagResult | None: A new FeatureFlagResult instance, or None if details is None. + +#### `posthog.types.FeatureFlagResult.from_value_and_payload` + +```python +from_value_and_payload(key: str, value: Union[FlagValue, None], payload: Any) -> Union[FeatureFlagResult, None] +``` + +Creates a FeatureFlagResult from a flag value and payload. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The unique identifier of the feature flag. | *required* +`value` | Union[FlagValue, None] | The value of the flag (string variant or boolean). | *required* +`payload` | Any | Additional data associated with the feature flag. | *required* + +**Returns:** + +Type | Description +---- | ----------- +Union[FeatureFlagResult, None] | Union[FeatureFlagResult, None]: A new FeatureFlagResult instance, or None if value is None. + +#### `posthog.types.FeatureFlagResult.get_value` + +```python +get_value() -> FlagValue +``` + +Returns the value of the flag. This is the variant if it exists, otherwise the enabled value. +This is the value we report as `$feature_flag_response` in the `$feature_flag_called` event. + +**Returns:** + +Name | Type | Description +---- | ---- | ----------- +`FlagValue` | FlagValue | Either a string variant or boolean value representing the flag's state. + +#### `posthog.types.FeatureFlagResult.key` + +```python +key: str +``` + +#### `posthog.types.FeatureFlagResult.payload` + +```python +payload: Optional[Any] +``` + +#### `posthog.types.FeatureFlagResult.reason` + +```python +reason: Optional[str] +``` + +#### `posthog.types.FeatureFlagResult.variant` + +```python +variant: Optional[str] +``` + +### `FeatureFlag` + +```python +FeatureFlag(key: str, enabled: bool, variant: Optional[str], reason: Optional[FlagReason], metadata: Union[FlagMetadata, LegacyFlagMetadata]) -> None +``` + +Detailed feature flag evaluation returned by the flags API. + +`enabled`, `variant` and `metadata.payload` are the value rendered the way +responses before `/flags?v=4` carried it, which the legacy getters return. + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`key` | str | Feature flag key. +`enabled` | bool | Whether the flag is enabled for the evaluated user or group. +`variant` | Optional[str] | Variant key for multivariate flags, otherwise `None`. +`reason` | Optional[FlagReason] | Optional reason metadata explaining the result. +`metadata` | Union[FlagMetadata, LegacyFlagMetadata] | Payload and other metadata returned by the API. + +**Functions:** + +Name | Description +---- | ----------- +`from_json` | Parse one flag record of a `/flags` response. +`from_value_and_payload` | Build a flag from its value and payload. A string value is a variant, so the flag is enabled. +`get_value` | Return the variant for a multivariate flag, otherwise whether the flag is enabled. + +#### `posthog.types.FeatureFlag.enabled` + +```python +enabled: bool +``` + +#### `posthog.types.FeatureFlag.from_json` + +```python +from_json(resp: Any) -> FeatureFlag +``` + +Parse one flag record of a `/flags` response. + +A record with a `value` member is a `/flags?v=4` record. A record +without one comes from an older server: its value is `variant`, or +`enabled` when there is no variant, and its config version is `1`. + +#### `posthog.types.FeatureFlag.from_value_and_payload` + +```python +from_value_and_payload(key: str, value: FlagValue, payload: Any) -> FeatureFlag +``` + +Build a flag from its value and payload. A string value is a variant, so the flag is enabled. + +#### `posthog.types.FeatureFlag.get_value` + +```python +get_value() -> FlagValue +``` + +Return the variant for a multivariate flag, otherwise whether the flag is enabled. + +#### `posthog.types.FeatureFlag.key` + +```python +key: str +``` + +#### `posthog.types.FeatureFlag.metadata` + +```python +metadata: Union[FlagMetadata, LegacyFlagMetadata] +``` + +#### `posthog.types.FeatureFlag.reason` + +```python +reason: Optional[FlagReason] +``` + +#### `posthog.types.FeatureFlag.variant` + +```python +variant: Optional[str] +``` + +### `FlagsAndPayloads` + +Bases: TypedDict + +Feature flag values and payloads keyed by feature flag key. + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`featureFlags` | Optional[dict[str, FlagValue]] | Each flag's value, keyed by flag key. +`featureFlagPayloads` | Optional[dict[str, Any]] | Each flag's payload, keyed by flag key. + +#### `posthog.types.FlagsAndPayloads.featureFlagPayloads` + +```python +featureFlagPayloads: Optional[dict[str, Any]] +``` + +#### `posthog.types.FlagsAndPayloads.featureFlags` + +```python +featureFlags: Optional[dict[str, FlagValue]] +``` + +### `UnresolvedFlagReason` + +Bases: str, Enum + +Why local evaluation left a flag without a value. + +Reported by `FeatureFlagEvaluations.unresolved_flags`. Inheriting from +`str` keeps the members directly comparable to their string values. When more +than one cause applies, the first reason in the order below is reported. + +
+Values + +EXPERIENCE_CONTINUITY: The flag has experience continuity enabled, which + local evaluation never resolves. Change the flag to fix it. +UNSUPPORTED_DEFINITION: The flag uses something the local evaluator does not + support, such as a static cohort, an unknown operator, a malformed value, + a dependency with no loaded definition or a circular dependency. Change + the flag or upgrade the SDK to fix it. +UNRESOLVED_DEPENDENCY: A flag this flag depends on has a loaded definition + but was unresolved. +MISSING_CONTEXT: The call did not pass a property, group property or device + id the flag needs, or passed it in an unusable form. Pass it to fix it. + +
+ +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`EXPERIENCE_CONTINUITY` | | +`MISSING_CONTEXT` | | +`UNRESOLVED_DEPENDENCY` | | +`UNSUPPORTED_DEFINITION` | | + +#### `posthog.types.UnresolvedFlagReason.EXPERIENCE_CONTINUITY` + +```python +EXPERIENCE_CONTINUITY = 'experience_continuity' +``` + +#### `posthog.types.UnresolvedFlagReason.MISSING_CONTEXT` + +```python +MISSING_CONTEXT = 'missing_context' +``` + +#### `posthog.types.UnresolvedFlagReason.UNRESOLVED_DEPENDENCY` + +```python +UNRESOLVED_DEPENDENCY = 'unresolved_dependency' +``` + +#### `posthog.types.UnresolvedFlagReason.UNSUPPORTED_DEFINITION` + +```python +UNSUPPORTED_DEFINITION = 'unsupported_definition' +``` + +### `InconclusiveMatchError` + +```python +InconclusiveMatchError(*args, reason: Optional[UnresolvedFlagReason] = None) +``` + +Bases: Exception + +Raised when local evaluation can't determine a flag's value. `reason` says +why, as reported by `FeatureFlagEvaluations.unresolved_flags`. + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`reason` | UnresolvedFlagReason | Why local evaluation couldn't resolve the flag. + +#### `posthog.feature_flags.InconclusiveMatchError.reason` + +```python +reason: UnresolvedFlagReason = UnresolvedFlagReason.UNSUPPORTED_DEFINITION +``` + +### `RequiresServerEvaluation` + +```python +RequiresServerEvaluation(*args, reason: Optional[UnresolvedFlagReason] = None) +``` + +Bases: Exception + +Raised when feature flag evaluation requires server-side data that is not +available locally (e.g., static cohorts). + +This error should propagate immediately to trigger API fallback, unlike +InconclusiveMatchError which allows trying other conditions. + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`reason` | UnresolvedFlagReason | Why the flag needs server-side evaluation. + +#### `posthog.feature_flags.RequiresServerEvaluation.reason` + +```python +reason: UnresolvedFlagReason = UnresolvedFlagReason.UNSUPPORTED_DEFINITION +``` + +### `Span` + +A handle to a span. + +Every method is safe to call on any handle, including after `end()` and +on the inert handles returned when tracing is off, so calling code never +branches on whether tracing is running. Entering a handle (`with span:`) +makes it the active span for the block and ends it on exit, recording an +`Exception` raised inside it. A `BaseException` that is not an +`Exception` (`GeneratorExit`, `CancelledError`, `KeyboardInterrupt`) +still ends the span but is not recorded as a failure. + +**Functions:** + +Name | Description +---- | ----------- +`add_event` | Add a timestamped event to the span. Ignored after `end()`. +`end` | End the span and queue it for export. Idempotent. +`record_exception` | Record an exception as an `exception` event and mark the span `error`. +`set_attribute` | Set one attribute on the span; last write wins. Ignored after `end()`. +`set_attributes` | Set several attributes at once; last write wins per key. Ignored after `end()`. +`set_status` | Set the span status to `"ok"` or `"error"`. Ignored after `end()`. +`traceparent` | The W3C `traceparent` header value to propagate, or `None`. +`tracestate` | The W3C `tracestate` header value to propagate, or `None`. +`update_name` | Rename the span, for a name only known after it started. Ignored after `end()`. + +#### `posthog.tracing.span.Span.add_event` + +```python +add_event(name: str, attributes: Optional[Mapping[str, Any]] = None, timestamp: Union[datetime, float, None] = None) -> Span +``` + +Add a timestamped event to the span. Ignored after `end()`. + +`timestamp` defaults to now; it accepts a `datetime`, or seconds +since the epoch as a `float` or `int`. Returns the span, so calls +chain. + +**Examples:** + +```python +span.add_event("cache.miss", {"key": "user:42"}) +``` + +#### `posthog.tracing.span.Span.end` + +```python +end(end_time: Union[datetime, float, None] = None) -> None +``` + +End the span and queue it for export. Idempotent. + +`end_time` defaults to now; it accepts a `datetime`, or seconds +since the epoch as a `float` or `int`, and is never earlier than +the start. Later calls, and later mutations, no-op. `with span:` +ends the span on exit, so call this only for spans started manually. + +**Examples:** + +```python +span = posthog.start_span("job") +try: + run_job() +finally: + span.end() +``` + +#### `posthog.tracing.span.Span.record_exception` + +```python +record_exception(exception: BaseException) -> Span +``` + +Record an exception as an `exception` event and mark the span `error`. + +Ignored after `end()`. The event carries `exception.type`, +`exception.message` and, for a raised exception, +`exception.stacktrace` (its last `max_attribute_value_length` +characters, so a chained traceback keeps the outermost exception and +may lose its cause). Returns the span, so calls chain. Inside +`with span:` a raised `Exception` is recorded automatically, so +this is for exceptions that are caught and handled. + +**Examples:** + +```python +try: + charge(card) +except PaymentError as e: + span.record_exception(e) +``` + +#### `posthog.tracing.span.Span.set_attribute` + +```python +set_attribute(key: str, value: Any) -> Span +``` + +Set one attribute on the span; last write wins. Ignored after `end()`. + +Returns the span, so calls chain. Prefer primitive values: strings, +booleans, integers and floats keep their type. Lists and mappings are +sent as OTLP arrays and maps, but PostHog stores them as serialized +strings. `None` removes the key; anything else is stringified. + +**Examples:** + +```python +span.set_attribute("http.status_code", 200).set_attribute("cache.hit", True) +``` + +#### `posthog.tracing.span.Span.set_attributes` + +```python +set_attributes(attributes: Mapping[str, Any]) -> Span +``` + +Set several attributes at once; last write wins per key. Ignored after `end()`. + +Returns the span, so calls chain. Same encoding as `set_attribute()`. + +**Examples:** + +```python +span.set_attributes({"db.system": "postgresql", "db.rows": 12}) +``` + +#### `posthog.tracing.span.Span.set_status` + +```python +set_status(code: str, message: Optional[str] = None) -> Span +``` + +Set the span status to `"ok"` or `"error"`. Ignored after `end()`. + +Unset by default. Any other `code` is ignored. `ok` is final for the +scoped form: an `Exception` raised inside `with span:` does not override +it. Returns the span, so calls chain. + +**Examples:** + +```python +span.set_status("error", "upstream timed out") +``` + +#### `posthog.tracing.span.Span.traceparent` + +```python +traceparent() -> Optional[str] +``` + +The W3C `traceparent` header value to propagate, or `None`. + +#### `posthog.tracing.span.Span.tracestate` + +```python +tracestate() -> Optional[str] +``` + +The W3C `tracestate` header value to propagate, or `None`. + +#### `posthog.tracing.span.Span.update_name` + +```python +update_name(name: str) -> Span +``` + +Rename the span, for a name only known after it started. Ignored after `end()`. + +Returns the span, so calls chain. + +**Examples:** + +```python +span = posthog.start_span("http.request") +span.update_name(f"{request.method} {route.pattern}") +``` + +### `Emails` + +```python +Emails(secret_key: Optional[str], *, host: Optional[str] = None, timeout: float = 15, max_retries: int = 2, disabled: bool = False, session: Optional[requests.Session] = None) -> None +``` + +Bases: _EmailsBase + +Sends transactional email from PostHog email templates. + +`host` is your PostHog app host. PostHog Cloud ingestion hosts map to the +app host, so `us.i.posthog.com` sends to `us.posthog.com`. Any other +host is used as given. + +Each send retries network errors, rate limits and an unavailable service up +to `max_retries` times, with the same idempotency key. Set +`max_retries=0` when a job runner already retries the send. `timeout` +is in seconds, per request. Pass `session` to send through your own +`requests.Session`, for example a test double. + +**Functions:** + +Name | Description +---- | ----------- +`close` | Close the HTTP connections this client opened. A `session` you passed in stays open. +`get` | Read what happened to an email that `send` queued. +`send` | Queue one email from a transactional email template. + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`disabled` | | +`host` | | +`max_retries` | | +`secret_key` | | +`timeout` | | + +#### `posthog.emails.Emails.close` + +```python +close() -> None +``` + +Close the HTTP connections this client opened. A `session` you passed in stays open. + +#### `posthog.emails.Emails.disabled` + +```python +disabled = disabled +``` + +#### `posthog.emails.Emails.get` + +```python +get(message_id: Union[str, UUID]) -> EmailStatus +``` + +Read what happened to an email that `send` queued. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`message_id` | Union[str, UUID] | The `id` of the `SendEmailResult` that `send` returned. | *required* + +**Returns:** + +Type | Description +---- | ----------- +EmailStatus | The email's latest status, for 30 days after the send. + +**Raises:** + +Type | Description +---- | ----------- +EmailError | PostHog did not answer with a status. `code` is `not_found` for an unknown ID, an email older than 30 days, or for a few seconds after the send. +ValueError | `message_id` is not a message ID. + +**Examples:** + +```python +result = emails.send(to="user@example.com", template_id="...") +status = emails.get(result.id) +if status.status == "bounced": + print(status.reason) +``` + +#### `posthog.emails.Emails.host` + +```python +host = determine_app_host(host) +``` + +#### `posthog.emails.Emails.max_retries` + +```python +max_retries = max_retries +``` + +#### `posthog.emails.Emails.secret_key` + +```python +secret_key = secret_key.strip() if isinstance(secret_key, str) else None +``` + +#### `posthog.emails.Emails.send` + +```python +send(*, to: str, template_id: Union[str, UUID], template_data: Optional[Mapping[str, Any]] = None, distinct_id: Optional[str] = None, idempotency_key: Optional[str] = None, max_retries: Optional[int] = None) -> SendEmailResult +``` + +Queue one email from a transactional email template. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`to` | str | One recipient email address. | *required* +`template_id` | Union[str, UUID] | The UUID of a template made transactional in PostHog. | *required* +`template_data` | Optional[Mapping[str, Any]] | Values for the template's `{{ variables.* }}` placeholders. Values are encoded the way capture encodes properties. | None +`distinct_id` | Optional[str] | The recipient's distinct ID in your product. The email's events go to this person. Without it, they go to a person whose distinct ID is the email address. | None +`idempotency_key` | Optional[str] | 1 to 255 visible ASCII characters. A send with a key that PostHog has already accepted queues nothing and returns the first send's result. Without it, the client makes a key that covers its own retries. Pass a key derived from what the email is for, such as `welcome:user_123`, to cover retries outside this call, too. | None +`max_retries` | Optional[int] | Overrides the client's maximum number of retries for this send. `0` turns retries off. | None + +**Returns:** + +Type | Description +---- | ----------- +SendEmailResult | The queued email. A result means PostHog queued the email, not that +SendEmailResult | it was delivered. + +**Raises:** + +Type | Description +---- | ----------- +EmailError | PostHog did not queue the email, or the client could not tell whether it did. +(TypeError, ValueError) | An argument has the wrong type or format. + +**Examples:** + +```python +emails.send( + to="user@example.com", + template_id="0190f6d2-7c1e-7b3a-9f5e-1c2d3e4f5a6b", + template_data={"name": "Ada"}, + distinct_id="user_123", + idempotency_key="welcome:user_123", +) +``` + +#### `posthog.emails.Emails.timeout` + +```python +timeout = timeout +``` + +### `EmailError` + +```python +EmailError(*, name: str, code: str, message: str, status_code: Optional[int] = None, why: Optional[str] = None, fix: Optional[str] = None, docs_url: Optional[str] = None, field: Optional[str] = None, retry_after: Optional[float] = None) -> None +``` + +Bases: Exception + +PostHog did not queue the email, or the client could not tell whether it did. + +Branch on `code`, PostHog's stable error code, or on `name`, a coarse +category of the failure. The message never contains the secret key or the +request body. + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`name` | | A coarse category of the failure. +`code` | | PostHog's stable error code. +`message` | | What went wrong. It never contains the secret key or the request body. +`status_code` | | The HTTP status of the response, or `None` when none arrived. +`why` | | PostHog's explanation of the failure, when it gave one. +`fix` | | What to change to fix it, when PostHog said. +`docs_url` | | A docs page about the error, when PostHog gave one. +`field` | | The request field the error is about, when there is one. +`retry_after` | | Seconds to wait before retrying, when PostHog said. + +#### `posthog.emails.EmailError.code` + +```python +code = code +``` + +#### `posthog.emails.EmailError.docs_url` + +```python +docs_url = docs_url +``` + +#### `posthog.emails.EmailError.field` + +```python +field = field +``` + +#### `posthog.emails.EmailError.fix` + +```python +fix = fix +``` + +#### `posthog.emails.EmailError.message` + +```python +message = message +``` + +#### `posthog.emails.EmailError.name` + +```python +name = name +``` + +#### `posthog.emails.EmailError.retry_after` + +```python +retry_after = retry_after +``` + +#### `posthog.emails.EmailError.status_code` + +```python +status_code = status_code +``` + +#### `posthog.emails.EmailError.why` + +```python +why = why +``` + +### `SendEmailResult` + +```python +SendEmailResult(id: str, replayed: bool = False, warnings: tuple[EmailWarning, ...] = ()) -> None +``` + +The queued email. `id` is its message ID. + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`id` | str | The email's message ID. +`replayed` | bool | `True` when the response says PostHog replayed an earlier send for the same `idempotency_key` instead of sending again. +`warnings` | tuple[EmailWarning, ...] | Warnings PostHog returned about the request. + +#### `posthog.emails.SendEmailResult.id` + +```python +id: str +``` + +#### `posthog.emails.SendEmailResult.replayed` + +```python +replayed: bool = False +``` + +#### `posthog.emails.SendEmailResult.warnings` + +```python +warnings: tuple[EmailWarning, ...] = () +``` diff --git a/references/posthog-python-8.md b/references/posthog-python-8.md new file mode 100644 index 000000000..5c887acc7 --- /dev/null +++ b/references/posthog-python-8.md @@ -0,0 +1,4363 @@ +--- +title: "PostHog Python SDK 8.x (experimental)" +sidebar: Docs +showTitle: true +contentMaxWidthClass: max-w-full +platformLogo: python +tableOfContents: + - value: "Getting started" + url: getting-started + depth: 0 + - value: "Traces" + url: traces + depth: 0 + - value: "Alias" + url: alias + depth: 0 + - value: "Capture" + url: capture + depth: 0 + - value: "Capture AI" + url: capture-ai + depth: 0 + - value: "Capture exception" + url: capture-exception + depth: 0 + - value: "Emails" + url: emails + depth: 0 + - value: "Flush" + url: flush + depth: 0 + - value: "Get feature flag" + url: get-feature-flag + depth: 0 + - value: "Get feature flag payload" + url: get-feature-flag-payload + depth: 0 + - value: "Get feature flag result" + url: get-feature-flag-result + depth: 0 + - value: "Get feature flags" + url: get-feature-flags + depth: 0 + - value: "Get feature flags and payloads" + url: get-feature-flags-and-payloads + depth: 0 + - value: "Evaluate flags" + url: evaluate-flags + depth: 0 + - value: "Group identify" + url: group-identify + depth: 0 + - value: "Identify" + url: identify + depth: 0 + - value: "Is feature enabled" + url: is-feature-enabled + depth: 0 + - value: "Setup" + url: setup + depth: 0 + - value: "Shutdown" + url: shutdown + depth: 0 + - value: "Flag definition loader" + url: flag-definition-loader + depth: 0 + - value: "SDK utils" + url: sdk-utils + depth: 0 + - value: "AsyncClient" + url: asyncclient + depth: 0 + - value: "Module functions" + url: module-functions + depth: 0 + - value: "Types" + url: types + depth: 0 +--- + +## Getting started + + +The Python SDK captures events, evaluates feature flags and reports errors from Python 3.10 or later. + +### Install + +```bash +pip install posthog +``` + +### Initialize + +Create one client for your app with your project token and host. Your project token and host are in your [project settings](https://app.posthog.com/project/settings). Keep the token in an environment variable rather than in code. + +```python +from posthog import Posthog + +posthog = Posthog("", host="") +``` + +### Capture an event + +```python +posthog.capture("user_signed_up", distinct_id="user_123", properties={"plan": "free"}) +``` + +`capture` queues the event and returns right away. A background thread sends it. + +### Shut down + +Call `shutdown()` before your process exits, so queued events are sent. + +```python +posthog.shutdown() +``` + +In an asyncio app, use `AsyncPosthog` as an async context manager instead. See [AsyncClient](#asyncclient). + +## Traces + +### `Client.start_span` + +```python +start_span(name: str, *, kind: Optional[str] = None, attributes: Optional[Mapping[str, Any]] = None, parent: Union[Span, str, None] = None, tracestate: Optional[str] = None, start_time: Union[datetime, float, None] = None) -> Span +``` + +Start a span for distributed tracing. Alpha. + +Returns a span handle. Use it as a context manager to make it the active +span for the block and end it on exit (recording a raised exception on +the way out); or call `end()` yourself for a span that cannot wrap a +block. Spans started inside the block nest under it automatically. +Always returns a usable handle, even when tracing is off, so calling +code never branches. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`name` | str | A low-cardinality operation name, e.g. `GET /users/:id`. Variable values belong in attributes, not the name. | *required* +`kind` | Optional[str] | `internal` (default), `server`, `client`, `producer` or `consumer`. | None +`attributes` | Optional[Mapping[str, Any]] | Initial attributes. | None +`parent` | Union[Span, str, None] | A span handle, or an inbound W3C `traceparent` header value to continue a remote trace. Defaults to the active span. A forked child starts with no active span; pass the parent span to continue a trace across a fork. | None +`tracestate` | Optional[str] | The inbound `tracestate` header accompanying a `traceparent` string `parent`; preserved and propagated. | None +`start_time` | Union[datetime, float, None] | A `datetime` or epoch seconds, to backdate the span. | None + +**Examples:** + +```python +posthog = Posthog("", traces={"service_name": "checkout-api"}) + +with posthog.start_span("POST /checkout", parent=request.headers.get("traceparent")) as span: + span.set_attribute("plan", user.plan) + with posthog.start_span("db.query", kind="client"): + ... + outgoing_headers = {"traceparent": span.traceparent()} +``` + +### `Client.get_active_span` + +```python +get_active_span() -> Optional[Span] +``` + +The span that is active in the current context, or `None`. Alpha. + +Only entering a span (`with posthog.start_span(...) as span:`) makes +it active; a span started manually is not. Use it to propagate the +trace to the next service: `span.traceparent()` is the header value. + +**Examples:** + +```python +span = posthog.get_active_span() +if span is not None: + headers["traceparent"] = span.traceparent() +``` + +## Alias + +### `Client.alias` + +```python +alias(previous_id: ID_TYPES, distinct_id: Optional[str], timestamp: Optional[Union[datetime, str]] = None, uuid: Optional[str] = None, disable_geoip: Optional[bool] = None, options: Optional[Dict[str, Any]] = None) -> Optional[str] +``` + +Create an alias between two distinct IDs. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`previous_id` | ID_TYPES | The previous distinct ID. Required - the call is dropped with a warning if it is missing or empty. | *required* +`distinct_id` | Optional[str] | The new distinct ID to alias to. Required - the call is dropped with a warning if it is missing or empty. The context distinct ID is never used here. | *required* +`timestamp` | Optional[Union[datetime, str]] | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | None +`uuid` | Optional[str] | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | None +`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this event. | None +`options` | Optional[Dict[str, Any]] | Capture options for this event, sent as given. | None + +**Examples:** + +```python +posthog.alias(previous_id='distinct_id', distinct_id='alias_id') +``` + +Note: This method will not raise exceptions. Errors are logged. + +## Capture + +### `Client.capture` + +```python +capture(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] +``` + +Captures an event manually. [Learn about capture best practices](https://posthog.com/docs/product-analytics/capture-events) + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`event` | str | The event name to capture. | *required* +`distinct_id` | | The distinct ID of the user. | +`properties` | | A dictionary of properties to include with the event. | +`timestamp` | | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | +`uuid` | | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | +`groups` | | A dictionary of group information. It merges into a `$groups` property and wins key by key. | +`flags` | | A FeatureFlagEvaluations snapshot from evaluate_flags(). The exact values from the snapshot are attached with no extra /flags request. | +`send_feature_flags` | | Deprecated. Prefer flags=... from evaluate_flags(). When truthy, evaluates flags during capture and attaches them to the event. | +`disable_geoip` | | Whether to disable GeoIP for this event. It wins over a `$geoip_disable` in context tags or `super_properties`. A `$geoip_disable` property in this call's `properties` wins over it. | +`options` | | Capture options for this event, such as `{"process_person_profile": False}`. Sent as given, for PostHog to validate. They override context options and `super_options`, which fill in before `before_send` runs. An option set at any layer wins over its legacy `$` property, such as `$process_person_profile`, set at any layer. A `None` option counts as unset. | + +**Examples:** + +```python +# Anonymous event +posthog.capture('some-anon-event') +``` +```python +# Context usage +from posthog import identify_context, new_context +with new_context(): + identify_context('distinct_id_of_the_user') + posthog.capture('user_signed_up') + posthog.capture('user_logged_in') + posthog.capture('some-custom-action', distinct_id='distinct_id_of_the_user') +``` +```python +# Set event properties +posthog.capture( + "user_signed_up", + distinct_id="distinct_id_of_the_user", + properties={ + "login_type": "email", + "is_free_trial": "true" + } +) +``` +```python +# Page view event +posthog.capture('$pageview', distinct_id="distinct_id_of_the_user", properties={'$current_url': 'https://example.com'}) +``` +```python +# Counter example (incorrect): capture keeps the first event for each uuid in a request and drops the later copies +event_id = str(uuid.uuid4()) +posthog.capture("order_placed", distinct_id="user_123", uuid=event_id) +posthog.capture("order_paid", distinct_id="user_123", uuid=event_id) # dropped as duplicate_event_uuid +``` + +## Capture AI + +### `Client.capture_ai` + +```python +capture_ai(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] +``` + +Capture an AI event on the dedicated AI capture endpoint. + +Beta: the signature is stable; operational limits (per-event size +cap, batching, endpoint) may change without notice. + +Takes the same arguments and returns the same value as `capture()`: +the event UUID, or None when the event was not admitted (disabled +client, or dropped by `before_send`). The event is queued on an +isolated AI lane with its own consumer pool and a higher per-event +size cap, posting to the dedicated AI ingestion endpoint. The payload +is sent as given — no redaction or truncation is applied here. + +## Capture exception + +### `Client.capture_exception` + +```python +capture_exception(exception: Optional[ExceptionArg], *, level: Optional[str] = None, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] +``` + +Capture an exception for error tracking. + +When OpenTelemetry is installed and a valid span is active, its trace and +span IDs are added as `$trace_id` and `$span_id` event properties, +unless the event, a context tag or `super_properties` sets either one. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`exception` | Optional[ExceptionArg] | The exception to capture. | *required* +`level` | Optional[str] | The `$exception_level`, such as `"warning"` or `"fatal"`. Defaults to `"error"`. An unknown value counts as unset. | None +`distinct_id` | | The distinct ID of the user. | +`properties` | | A dictionary of additional properties. Reserved exception properties, such as `$exception_list` and `$exception_level`, are ignored. | +`flags` | | A `FeatureFlagEvaluations` snapshot from `evaluate_flags()`. Attaches those exact flag values to the captured `$exception` event. | +`send_feature_flags` | | Deprecated. Pass `flags` from `evaluate_flags()` instead. | +`disable_geoip` | | Whether to disable GeoIP for this event. | + +**Examples:** + +```python +try: + # Some code that might fail + pass +except Exception as e: + posthog.capture_exception(e, 'user_distinct_id', properties=additional_properties) +``` + +## Emails + +### `Client.emails` + +```python +emails: Emails +``` + +Send transactional email from PostHog email templates, and read the +status of each email sent with `get`. + +Needs `secret_key`, a project secret API key with the `hog_flow:write` +scope. Sends go to `app_host`, or to the PostHog Cloud app host of +`host`. `email_request_timeout_seconds` and `email_request_max_retries` +configure each send. A disabled or shut-down client sends nothing. See +`posthog.emails` for setup. + +**Examples:** + +```python +client.emails.send( + to="user@example.com", + template_id="0190f6d2-7c1e-7b3a-9f5e-1c2d3e4f5a6b", + template_data={"name": "Ada"}, + distinct_id="user_123", +) +``` + +## Flush + +### `Client.flush` + +```python +flush(timeout_seconds: Optional[float] = 10) -> None +``` + +Attempt immediate delivery of queued events, ended spans and autocaptured metrics, keeping the client reusable. + +For a client shared across requests or serverless invocations (including +the module-level client), call `flush()` before a short-lived invocation +returns. Use `shutdown()` instead only for final process cleanup or when +disposing of a client that will not be reused. Flush does not stop the +background workers or feature flag poller, and does not end open spans. + +Returning does not guarantee server receipt: pending work can remain after +a timeout or span export failure, and failed events may be dropped by the +consumers. Calls made directly from SDK callbacks such as `on_error` are +deferred rather than blocking the worker that invoked the callback. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`timeout_seconds` | Optional[float] | Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass `None` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently. The span exporter waits up to this many seconds for an in-flight flush, then uses a fresh drain budget once it acquires the lock. If the lock wait times out, the in-flight flush is left to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. Each span request is bounded by the client's `timeout` in seconds, and the last request is not cut short. This is therefore not a strict wall-clock limit for the whole call. With `None`, a retriable span failure is left for a later flush rather than retried within this call. | 10 + +**Examples:** + +```python +posthog.capture('event_name', distinct_id='user_id') +# Before a serverless invocation returns; keep the shared client open. +posthog.flush(timeout_seconds=10) +``` + +## Get feature flag + +### `Client.get_feature_flag` + +```python +get_feature_flag(key: str, distinct_id: ID_TYPES, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[FlagValue] +``` + +Get multivariate feature flag value for a user. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The feature flag key. | *required* +`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None +`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None +`only_evaluate_locally` | bool | Whether to only evaluate locally. | False +`send_feature_flag_events` | bool | Whether to send feature flag events. | True +`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None +`device_id` | Optional[str] | The device ID for this request. | None + +**Examples:** + +```python +enabled_variant = posthog.get_feature_flag('flag-key', 'distinct_id_of_your_user') +if enabled_variant == 'variant-key': # replace 'variant-key' with the key of your variant + # Do something differently for this user + # Optional: fetch the payload + matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user') +``` + +## Get feature flag payload + +### `Client.get_feature_flag_payload` + +```python +get_feature_flag_payload(key: str, distinct_id: ID_TYPES, *, match_value: Optional[FlagValue] = None, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = False, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[object] +``` + +Get the payload for a feature flag. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The feature flag key. | *required* +`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* +`match_value` | Optional[FlagValue] | The specific flag value to get payload for. | None +`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None +`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None +`only_evaluate_locally` | bool | Whether to only evaluate locally. | False +`send_feature_flag_events` | bool | Deprecated. Use get_feature_flag() instead if you need events. | False +`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None +`device_id` | Optional[str] | The device ID for this request. | None + +**Returns:** + +Type | Description +---- | ----------- +Optional[object] | The payload associated with the matched feature flag value, or None. +Optional[object] | This method returns the payload only, not the FeatureFlagResult wrapper +Optional[object] | used internally to compute it. + +**Examples:** + +```python +is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user') + +if is_my_flag_enabled: + # Do something differently for this user + # Optional: fetch the payload + matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user') +``` + +## Get feature flag result + +### `Client.get_feature_flag_result` + +```python +get_feature_flag_result(key: str, distinct_id: ID_TYPES, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[FeatureFlagResult] +``` + +Get a FeatureFlagResult object which contains the flag result and payload for a key by evaluating locally or remotely +depending on whether local evaluation is enabled and the flag can be locally evaluated. +This also captures the `$feature_flag_called` event unless `send_feature_flag_events` is `False`. + +**Examples:** + +```python +flag_result = posthog.get_feature_flag_result('flag-key', 'distinct_id_of_your_user') +if flag_result and flag_result.get_value() == 'variant-key': + # Do something differently for this user + # Optional: fetch the payload + matched_flag_payload = flag_result.payload +``` + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The feature flag key. | *required* +`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None +`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None +`only_evaluate_locally` | bool | Whether to only evaluate locally. | False +`send_feature_flag_events` | bool | Whether to send feature flag events. | True +`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None +`device_id` | Optional[str] | The device ID for this request. | None + +**Returns:** + +Type | Description +---- | ----------- +Optional[FeatureFlagResult] | Optional[FeatureFlagResult]: The feature flag result or None if disabled/not found. + +## Get feature flags + +### `Client.get_feature_variants` + +```python +get_feature_variants(distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, disable_geoip: Optional[bool] = None, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None) -> dict[str, Union[bool, str]] +``` + +Get feature flag variants for a user. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None +`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None +`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None +`flag_keys_to_evaluate` | Optional[list[str]] | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | None +`device_id` | Optional[str] | The device ID for this request. | None + +### `Client.get_all_flags` + +```python +get_all_flags(distinct_id: ID_TYPES, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None) -> Optional[dict[str, Union[bool, str]]] +``` + +Get all feature flags for a user. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None +`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None +`only_evaluate_locally` | bool | Whether to only evaluate locally. | False +`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None +`flag_keys_to_evaluate` | Optional[list[str]] | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | None +`device_id` | Optional[str] | The device ID for this request. | None + +**Examples:** + +```python +posthog.get_all_flags('distinct_id_of_your_user') +``` + +## Get feature flags and payloads + +### `Client.get_feature_flags_and_payloads` + +```python +get_feature_flags_and_payloads(distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, disable_geoip: Optional[bool] = None, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None) -> FlagsAndPayloads +``` + +Get feature flags and payloads for a user. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None +`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None +`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None +`flag_keys_to_evaluate` | Optional[list[str]] | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | None +`device_id` | Optional[str] | The device ID for this request. | None + +**Examples:** + +```python +result = posthog.get_feature_flags_and_payloads('') +``` + +### `Client.get_all_flags_and_payloads` + +```python +get_all_flags_and_payloads(distinct_id: ID_TYPES, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None) -> FlagsAndPayloads +``` + +Get all feature flags and their payloads for a user. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None +`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None +`only_evaluate_locally` | bool | Whether to only evaluate locally. | False +`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None +`flag_keys_to_evaluate` | Optional[list[str]] | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | None +`device_id` | Optional[str] | The device ID for this request. | None + +**Examples:** + +```python +posthog.get_all_flags_and_payloads('distinct_id_of_your_user') +``` + +## Evaluate flags + +### `Client.evaluate_flags` + +```python +evaluate_flags(distinct_id: Optional[ID_TYPES] = None, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, flag_keys: Optional[List[str]] = None, device_id: Optional[str] = None) -> FeatureFlagEvaluations +``` + +Evaluate all feature flags for a user in a single call and return a +`FeatureFlagEvaluations` snapshot. Branch on `.is_enabled()` / +`.get_flag()` and pass the same snapshot to `capture` via the +`flags` option so events carry the exact flag values the code branched on. + +Prefer this over repeated `get_feature_flag()` calls and over +`capture(send_feature_flags=True)` — it consolidates flag evaluation into +a single `/flags` request per incoming request. + +Local evaluation is transparent: when the poller resolves a flag, the +snapshot's `$feature_flag_called` events are tagged `locally_evaluated=True` +and reason `"Evaluated locally"`. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | Optional[ID_TYPES] | The user's distinct ID. If `None`, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot. | None +`groups` | Optional[Mapping[str, Union[str, int]]] | Mapping of group type to group key. | None +`person_properties` | Optional[Dict[str, Any]] | Person properties to use for evaluation. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties keyed by group type. | None +`only_evaluate_locally` | bool | If True, never fall back to remote evaluation — flags that can't be evaluated locally are omitted from the snapshot and listed with a reason in its `unresolved_flags`. | False +`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup. | None +`flag_keys` | Optional[List[str]] | Optional list that scopes local evaluation, the underlying `/flags` request, and the returned snapshot. When omitted or `None`, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per `evaluate_flags` call unless `only_evaluate_locally` is True. If the server also does not know the key, it is omitted from the snapshot. | None +`device_id` | Optional[str] | Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes. | None + +**Returns:** + +Type | Description +---- | ----------- +FeatureFlagEvaluations | A `FeatureFlagEvaluations` snapshot. + +**Examples:** + +```python +flags = posthog.evaluate_flags( + "user_123", + person_properties={"plan": "enterprise"}, +) +if flags.is_enabled("new-dashboard"): + render_new_dashboard() +posthog.capture("page_viewed", distinct_id="user_123", flags=flags) +``` +```python +# Counter example (incorrect): the event can carry different flag values than the code branched on +if posthog.get_feature_flag("new-dashboard", "user_123"): + render_new_dashboard() +posthog.capture("page_viewed", distinct_id="user_123", send_feature_flags=True) # evaluates flags again +``` + +## Group identify + +### `Client.group_identify` + +```python +group_identify(group_type: str, group_key: str, properties: Optional[Dict[str, Any]] = None, timestamp: Optional[Union[datetime, str]] = None, uuid: Optional[Union[str, UUID]] = None, disable_geoip: Optional[bool] = None, distinct_id: Optional[ID_TYPES] = None, options: Optional[Dict[str, Any]] = None) -> Optional[str] +``` + +Identify a group and set its properties. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`group_type` | str | The type of group (e.g., 'company', 'team'). Required - the call is dropped with a warning if it is missing or empty. | *required* +`group_key` | str | The unique identifier for the group. Required - the call is dropped with a warning if it is missing or empty. | *required* +`properties` | Optional[Dict[str, Any]] | A dictionary of properties to set on the group. | None +`timestamp` | Optional[Union[datetime, str]] | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | None +`uuid` | Optional[Union[str, UUID]] | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | None +`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this event. | None +`distinct_id` | Optional[ID_TYPES] | The distinct ID of the user performing the action. Falls back to the context distinct ID, then to `$_`. | None +`options` | Optional[Dict[str, Any]] | Capture options for this event, sent as given. | None + +**Examples:** + +```python +posthog.group_identify('company', 'company_id_in_your_db', { + 'name': 'Awesome Inc.', + 'employees': 11 +}) +``` + +Note: This method will not raise exceptions. Errors are logged. + +## Identify + +### `Client.set` + +```python +set(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str] +``` + +Set properties on a person profile. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | | The distinct ID of the user. | +`properties` | | A dictionary of properties to set. | +`timestamp` | | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | +`uuid` | | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | +`disable_geoip` | | Whether to disable GeoIP for this event. | + +**Examples:** + +```python +# Set with distinct id +posthog.set(distinct_id='user123', properties={'name': 'Max Hedgehog'}) +``` + +Note: This method will not raise exceptions. Errors are logged. + +### `Client.set_once` + +```python +set_once(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str] +``` + +Set properties on a person profile only if they haven't been set before. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | | The distinct ID of the user. | +`properties` | | A dictionary of properties to set once. | +`timestamp` | | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | +`uuid` | | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | +`disable_geoip` | | Whether to disable GeoIP for this event. | + +**Examples:** + +```python +posthog.set_once(distinct_id='user123', properties={'initial_signup_date': '2024-01-01'}) +``` + +Note: This method will not raise exceptions. Errors are logged. + +## Is feature enabled + +### `Client.feature_enabled` + +```python +feature_enabled(key: str, distinct_id: ID_TYPES, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[bool] +``` + +Check if a feature flag is enabled for a user. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The feature flag key. | *required* +`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None +`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None +`only_evaluate_locally` | bool | Whether to only evaluate locally. | False +`send_feature_flag_events` | bool | Whether to send feature flag events. | True +`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None +`device_id` | Optional[str] | The device ID for this request. | None + +**Examples:** + +```python +is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user') +if is_my_flag_enabled: + # Do something differently for this user + # Optional: fetch the payload + matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user') +``` + +## Setup + +### `Client(...)` + +```python +__init__(project_api_key: str, host: str = None, *, debug: str = False, max_queue_size: str = 10000, send: str = True, on_error: str = None, flush_at: str = 100, flush_interval: str = 5.0, max_retries: str = 3, sync_mode: str = False, timeout: float = 15, thread: float = 1, poll_interval: float = 30, personal_api_key: float = None, disabled: float = False, disable_geoip: float = True, is_server: float = True, historical_migration: float = False, feature_flags_request_timeout_seconds: float = 3, feature_flags_request_max_retries: float = 1, super_properties: float = None, super_options: float = None, enable_exception_autocapture: float = False, log_captured_exceptions: float = False, project_root: float = None, privacy_mode: float = False, before_send: float = None, flag_fallback_cache_url: float = None, enable_local_evaluation: float = True, flag_definition_cache_provider: Optional[FlagDefinitionCacheProvider] = None, capture_exception_code_variables: Optional[FlagDefinitionCacheProvider] = False, code_variables_mask_patterns: Optional[FlagDefinitionCacheProvider] = None, code_variables_ignore_patterns: Optional[FlagDefinitionCacheProvider] = None, code_variables_mask_url_credentials: Optional[FlagDefinitionCacheProvider] = None, code_variables_detect_secrets: Optional[FlagDefinitionCacheProvider] = None, in_app_modules: list[str] | None = None, enable_exception_autocapture_rate_limiting: list[str] | None = False, exception_autocapture_bucket_size: list[str] | None = ExceptionCapture.DEFAULT_BUCKET_SIZE, exception_autocapture_refill_rate: list[str] | None = ExceptionCapture.DEFAULT_REFILL_RATE, exception_autocapture_refill_interval_seconds: list[str] | None = ExceptionCapture.DEFAULT_REFILL_INTERVAL_SECONDS, capture_compression: Optional[Union[CaptureCompression, str]] = None, capture_ai_compression: Optional[Union[CaptureCompression, str]] = None, capture_ai_max_queue_size: int = 1000, capture_ai_timeout: float = 30, capture_ai_max_event_bytes: int = AI_MAX_MSG_SIZE, secret_key: int = None, metrics: Optional[dict] = None, enable_full_ai_capture: Optional[dict] = False, capture_trace_context: Optional[dict] = False, _use_ai_lane: Optional[dict] = False, _enable_multimodal_capture: Optional[dict] = False, traces: Optional[dict] = None, app_host: Optional[str] = None, email_request_timeout_seconds: float = 15, email_request_max_retries: int = 2) +``` + +Initialize a new PostHog client instance. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`project_api_key` | str | PostHog project API key/token. | *required* +`host` | | PostHog host. Defaults to the US ingestion endpoint when not set. App hosts such as `https://us.posthog.com` are mapped to the corresponding ingestion host. | None +`debug` | | Enable verbose SDK logging and re-raise errors from public API methods. | False +`max_queue_size` | | Maximum number of analytics events buffered before upload. AI events use `capture_ai_max_queue_size`. | 10000 +`send` | | If False, queueing succeeds but events are not sent. | True +`on_error` | | Optional callback `(error, batch)` invoked when an upload fails: by background consumers, or on the calling thread in `sync_mode`. Capture failures arrive as `CaptureError`. Without it, each failed batch logs one aggregate line. A capture that fails inside the callback logs that line instead of calling it again. Keep it short and non-blocking. Calling lifecycle methods directly is safe and deferred, but do not start another thread or task that calls `flush()`, `join()`, or `shutdown()` and then wait for it from the callback. | None +`flush_at` | | Number of queued events that triggers a batch upload. | 100 +`flush_interval` | | Maximum seconds a background consumer waits before flushing a partial batch. | 5.0 +`max_retries` | | Number of upload retries. Values below 0 are treated as 0. | 3 +`sync_mode` | | If True, send each event synchronously instead of using background worker threads. This blocks the calling thread; in asyncio applications such as FastAPI, use `AsyncPosthog` instead. | False +`timeout` | float | HTTP request timeout in seconds for event uploads. | 15 +`thread` | | Number of background consumer threads. | 1 +`poll_interval` | | Seconds between local feature flag definition refreshes. | 30 +`secret_key` | | A Personal API Key or Project Secret API Key, used to authenticate local feature flag evaluation, remote config payloads, and decrypted flag payloads. Example::
posthog.Client(project_api_key, secret_key="phx_...") | None +`personal_api_key` | | Deprecated alias for `secret_key`. Still honored for backwards compatibility; prefer `secret_key`, which also accepts a Project Secret API Key. | None +`disabled` | | If True, disable captures and API requests. Useful in tests. | False +`disable_geoip` | | Whether to disable server-side GeoIP enrichment. Defaults to True. Events get `$geoip_disable` only when the event, context tags and `super_properties` leave it unset. | True +`is_server` | | Whether events are emitted from a server-side runtime. Defaults to True; set to False when using the SDK as a client/CLI so the device OS is attributed to the person normally. Events get `$is_server` only when the event, context tags and `super_properties` leave it unset. | True +`historical_migration` | | Mark events as historical migration imports. | False +`feature_flags_request_timeout_seconds` | float | Timeout in seconds for feature flag and remote config requests. | 3 +`feature_flags_request_max_retries` | | Number of retries for feature flag requests after network, transport, or timeout failures. Defaults to 1. Set to 0 to disable retries. | 1 +`app_host` | Optional[str] | Your PostHog app host, such as `https://posthog.example.com`. `emails` sends there. Needed only when `host` is not PostHog Cloud, for example a reverse proxy, which must not receive the secret key. | None +`email_request_timeout_seconds` | float | Timeout in seconds for each email send request. Defaults to 15. | 15 +`email_request_max_retries` | int | Number of retries for an email send after a network error, a rate limit, or an unavailable service. Defaults to 2. Set to 0 when a job runner already retries the send. | 2 +`super_properties` | | Properties for every captured event. They fill only keys the event leaves unset, before `before_send` runs. `$set`, `$set_once`, `$groups` and `$group_set` fill one level deep. An event's own properties and context tags override them, and `before_send` can change or remove them. | None +`super_options` | | Capture options for every captured event, such as `{"cookieless_mode": True}`. They fill only options the event leaves unset, before `before_send` runs. An event's own `options` and context options override them, and `before_send` can change them. They also win over an event's legacy property for the same key, such as `$cookieless_mode`, so pass per-event overrides of that key as `options`. | None +`enable_exception_autocapture` | | Automatically capture uncaught exceptions. | False +`log_captured_exceptions` | | Also log exceptions captured by error tracking. | False +`project_root` | | Root path used to determine in-app stack frames for captured exceptions. Defaults to the current working directory. | None +`privacy_mode` | | For AI observability, capture usage metadata without prompt inputs or outputs. | False +`enable_full_ai_capture` | | Capture full AI content in PostHog AI wrapper events: skips string truncation and passes media (base64/data URIs) through unredacted. `privacy_mode` always wins. Wrapper events use the AI capture endpoint whether or not this is set. Defaults to False. | False +`before_send` | | Optional callback that can modify or drop events before upload. Return `None` to drop an event. Context tags, context options, `super_properties`, `super_options` and the values the SDK adds, such as `$is_server`, `$geoip_disable` and `$os`, fill in before it runs, so it can change or remove them. | None +`flag_fallback_cache_url` | | Optional feature flag fallback cache URL, such as `memory://local/?ttl=300&size=10000` or a Redis URL. | None +`enable_local_evaluation` | | Whether to poll feature flag definitions for local evaluation when a personal API key is configured. | True +`flag_definition_cache_provider` | Optional[FlagDefinitionCacheProvider] | Optional external cache provider for sharing feature flag definitions across workers. | None +`capture_exception_code_variables` | | Capture local variable values on exception stack frames. | False +`capture_trace_context` | | When OpenTelemetry is installed and a valid span is active at capture time, add its trace and span IDs as `$trace_id` and `$span_id` properties to events captured with `capture()` and `capture_ai()`, so they can be correlated with backend traces. They fill after context tags and `super_properties`, before `before_send`. A `$trace_id` or `$span_id` from the event, a context tag or `super_properties` stops the SDK adding either ID. Exception events (`capture_exception`) always attach these IDs regardless of this setting. Defaults to False. | False +`traces` | Optional[dict] | Config dict for distributed tracing: `service_name`, `service_version`, `environment`, `resource_attributes`, `flush_interval` (5 s), `max_queue_size` (2048), `max_export_batch_size` (512), `max_live_spans` (10000), `max_span_age` (3600 s), `max_attributes_per_span` (128), `max_events_per_span` (128), `max_attribute_value_length` (8192). `before_span_send` is a callable, or a list run in order, that receives each finished span as a dict (`trace_id`, `span_id` and `parent_span_id` are read-only) and returns it, edited, or `None` to drop it; a hook that raises drops the span. Tracing is off until this is provided. Spans export on a background timer even with `sync_mode`; serverless handlers should call `flush()` before returning. Defaults to None. | None +`code_variables_mask_patterns` | | Variable-name patterns to mask when capturing code variables. | None +`code_variables_ignore_patterns` | | Variable-name patterns to omit when capturing code variables. | None +`code_variables_mask_url_credentials` | | Scrub credentials embedded in URLs/DSNs (e.g. `user:pass@host`) from captured code variables, regardless of the surrounding variable name. Defaults to True. | None +`code_variables_detect_secrets` | | Last-resort entropy-based detection that redacts high-entropy secret-looking values (API keys, tokens, strong passwords) sitting in innocuously-named variables, after the name and URL checks. Skips structured ids (UUIDs, ObjectIds, hashes). Defaults to True. | None +`in_app_modules` | list[str] \| None | Module/package prefixes treated as in-app frames in captured exceptions. | None +`enable_exception_autocapture_rate_limiting` | | Rate limit autocaptured exceptions client-side with a token bucket per exception type. Disabled by default. | False +`exception_autocapture_bucket_size` | | Maximum burst of autocaptured exceptions allowed per exception type (token bucket size, clamped to 0-100). | DEFAULT_BUCKET_SIZE +`exception_autocapture_refill_rate` | | Tokens restored per refill interval for each exception type's bucket. | DEFAULT_REFILL_RATE +`exception_autocapture_refill_interval_seconds` | | Seconds between token refills for autocaptured exception rate limiting. | DEFAULT_REFILL_INTERVAL_SECONDS +`capture_compression` | Optional[Union[CaptureCompression, str]] | Request-body compression for `capture()` uploads. `CaptureCompression.GZIP` or `DEFLATE` (or the strings `"gzip"`/`"deflate"`). When omitted, the `POSTHOG_CAPTURE_COMPRESSION` env var is consulted, then no compression. | None +`capture_ai_compression` | Optional[Union[CaptureCompression, str]] | Request-body compression for `capture_ai()` uploads, set independently of `capture_compression`. Defaults to no compression, and the env var does not apply. `CaptureCompression.ZSTD` suits large AI payloads. | None +`capture_ai_max_queue_size` | int | Maximum number of AI events buffered before upload. Defaults to 1000, lower than `max_queue_size` because AI events are much larger. | 1000 +`capture_ai_timeout` | float | Seconds allowed for one AI upload request. Defaults to 30, longer than `timeout` because AI batches are much larger. | 30 +`capture_ai_max_event_bytes` | int | Largest serialized AI event the SDK sends; a larger one is dropped with an error log. Defaults to the AI endpoint's ceiling plus envelope headroom, and may only be lowered. | AI_MAX_MSG_SIZE + +**Examples:** + +```python +from posthog import Posthog + +posthog = Posthog('', host='') +``` + +## Shutdown + +### `Client.join` + +```python +join() -> None +``` + +Attempt to process queued events and end the consumer threads. Do not use directly, call `shutdown()` instead. + +Failed or undrainable events may be dropped and reported through logging +or `on_error`; returning does not guarantee server receipt. Lifecycle +cleanup is attempted once, and cleanup failures are logged without retry. + +**Examples:** + +```python +posthog.join() +``` + +### `Client.shutdown` + +```python +shutdown() -> None +``` + +Attempt final delivery and permanently shut down the client. + +Call this for final process cleanup or when disposing of a client that +will not be reused. It closes capture lanes, stops background workers and +the feature flag poller, and tears down integrations. Do not call it at the +end of each request or serverless invocation when the client is shared; +call `flush()` before returning instead. A shut-down client cannot be +reused; create a new client if needed. + +Normally this method blocks until queued events have been attempted and +cleanup finishes. Failed or undrainable events may be dropped and +reported through logging or `on_error`; returning does not guarantee +server receipt. Event draining has no timeout. Queued spans get one final +flush with up to 30 seconds waiting for an in-flight flush, then a fresh +30-second drain budget; the last HTTP request is not cut short. Any spans +it cannot send are discarded with a warning, as are spans still open. +Lifecycle cleanup is attempted once, and cleanup failures +are logged without retry. When called directly from an SDK callback such as +`on_error`, shutdown is deferred to avoid blocking the worker that +invoked the callback. If the callback must coordinate a blocking +shutdown, have it signal an +application-owned thread and return before that thread calls shutdown. +Do not wait inside the callback for another thread or task that calls a +lifecycle method. + +**Examples:** + +```python +posthog.shutdown() +``` + +## Flag definition loader + +### `Client.load_feature_flags` + +```python +load_feature_flags() +``` + +Load feature flags for local evaluation. + +**Examples:** + +```python +posthog.load_feature_flags() +``` + +### `Client.feature_flag_definitions` + +```python +feature_flag_definitions() +``` + +Return feature flag definitions loaded for local evaluation. + +**Returns:** + +Type | Description +---- | ----------- + | The currently loaded feature flag definitions, or `None` before + | local evaluation has loaded definitions. + +## SDK utils + +### `Client.queue` + +```python +queue: Queue +``` + +The analytics lane's queue (kept for backwards compatibility). + +### `Client.consumers` + +```python +consumers: Optional[List[Consumer]] +``` + +Flat list of the lanes' consumers, analytics first (kept for backwards compatibility). + +### `Client.new_context` + +```python +new_context(fresh = False, capture_exceptions: Optional[bool] = None) +``` + +Create a new context for managing shared state. Learn more about [contexts](/docs/libraries/python#contexts). + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`fresh` | | Whether to create a fresh context that doesn't inherit from parent. | False +`capture_exceptions` | Optional[bool] | Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting. | None + +**Examples:** + +```python +with client.new_context(): + client.identify_context('') + client.capture('event_name') +``` + +
+Category + +Contexts + +
+ +### `Client.scoped` + +```python +scoped(fresh = False, capture_exceptions: Optional[bool] = None) +``` + +Decorator that creates a new context for the wrapped function using this client. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`fresh` | | Whether to create a fresh context that doesn't inherit from parent. | False +`capture_exceptions` | Optional[bool] | Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting. | None + +
+Category + +Contexts + +
+ +### `Client.tag` + +```python +tag(name: str, value: Any) -> None +``` + +Add a tag to the current context. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`name` | str | The tag key. | *required* +`value` | Any | The tag value. | *required* + +
+Category + +Contexts + +
+ +### `Client.get_tags` + +```python +get_tags() -> Dict[str, Any] +``` + +Get all tags from the current context. + +**Returns:** + +Type | Description +---- | ----------- +Dict[str, Any] | Dict of all tags in the current context. + +
+Category + +Contexts + +
+ +### `Client.set_context_option` + +```python +set_context_option(key: str, value: Any) -> None +``` + +Set a capture option for every event captured within the current context. + +Context options fill options an event leaves unset, before +`before_send` runs, so the hook sees them. They override +`super_options`. An event's own `options` override them, and +`before_send` can change them. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The option name, such as `"process_person_profile"`. | *required* +`value` | Any | The option value, sent as given. | *required* + +
+Category + +Contexts + +
+ +### `Client.get_context_options` + +```python +get_context_options() -> Dict[str, Any] +``` + +Get all capture options from the current context. + +**Returns:** + +Type | Description +---- | ----------- +Dict[str, Any] | Dict of all capture options in the current context. + +
+Category + +Contexts + +
+ +### `Client.identify_context` + +```python +identify_context(distinct_id: str) -> None +``` + +Identify the current context with a distinct ID. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | str | The distinct ID to associate with the current context and its children. | *required* + +
+Category + +Contexts + +
+ +### `Client.set_context_session` + +```python +set_context_session(session_id: str) -> None +``` + +Set the session ID for the current context. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`session_id` | str | The session ID to associate with the current context and its children. | *required* + +
+Category + +Contexts + +
+ +### `Client.set_context_device_id` + +```python +set_context_device_id(device_id: str) -> None +``` + +Set the device ID for the current context. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`device_id` | str | The device ID to associate with the current context and its children. | *required* + +
+Category + +Contexts + +
+ +### `Client.feature_flags` + +```python +feature_flags +``` + +Get the local evaluation feature flags. + +### `Client.get_feature_payloads` + +```python +get_feature_payloads(distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, disable_geoip: Optional[bool] = None, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None) -> dict[str, Optional[str]] +``` + +Get feature flag payloads for a user, preserving valid serialized JSON. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None +`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None +`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None +`flag_keys_to_evaluate` | Optional[list[str]] | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | None +`device_id` | Optional[str] | The device ID for this request. | None + +**Examples:** + +```python +payloads = posthog.get_feature_payloads('') +``` + +
+Category + +Feature flags + +
+ +### `Client.get_flags_decision` + +```python +get_flags_decision(distinct_id: Optional[ID_TYPES] = None, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, disable_geoip: Optional[bool] = None, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None) -> FlagsResponse +``` + +Get feature flags decision. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | Optional[ID_TYPES] | The distinct ID of the user. | None +`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None +`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None +`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None +`flag_keys_to_evaluate` | Optional[list[str]] | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | None +`device_id` | Optional[str] | The device ID for this request. | None + +**Examples:** + +```python +decision = posthog.get_flags_decision('user123') +``` + +
+Category + +Feature flags + +
+ +### `Client.metrics` + +```python +metrics: PostHogMetrics +``` + +The `posthog.metrics` API: a statsd-style pre-aggregating metrics client — alpha. + +Samples fold into per-series aggregates in memory and flush as one OTLP +data point per series per window, so recording from hot paths is cheap. +Configure via the `metrics` client option; pending metrics flush on +`shutdown()`. + +**Examples:** + +```python +client = Posthog("", metrics={"service_name": "billing-worker"}) +client.metrics.count("invoices.processed", 1, attributes={"plan": "pro"}) +client.metrics.gauge("queue.depth", 42) +client.metrics.histogram("job.duration", 187, unit="ms") +``` + +### `Client.get_remote_config_payload` + +```python +get_remote_config_payload(key: str) +``` + +Get the payload for a remote config feature flag. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The remote config feature flag key. | *required* + +**Returns:** + +Type | Description +---- | ----------- + | The payload associated with the feature flag, or `None` if the + | client is disabled, no personal API key is configured, or the request + | fails. Encrypted payloads are decrypted by PostHog before being + | returned. + +
+Note + +Requires `secret_key` for authentication. + +
+ +
+Category + +Feature flags + +
+ +### `Client.get_feature_flag_evaluation_runtime` + +```python +get_feature_flag_evaluation_runtime(key: str) -> Optional[FeatureFlagEvaluationRuntime] +``` + +Return where a locally loaded feature flag is meant to be evaluated. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The feature flag key. | *required* + +**Returns:** + +Type | Description +---- | ----------- +Optional[FeatureFlagEvaluationRuntime] | The flag's evaluation runtime, or `None` when local evaluation has +Optional[FeatureFlagEvaluationRuntime] | not loaded a definition for this key. A definition that carries no +Optional[FeatureFlagEvaluationRuntime] | runtime reports `FeatureFlagEvaluationRuntime.ALL`, the default +Optional[FeatureFlagEvaluationRuntime] | PostHog applies. + +**Examples:** + +```python +from posthog import FeatureFlagEvaluationRuntime + +runtime = posthog.get_feature_flag_evaluation_runtime("my-flag") +if runtime is FeatureFlagEvaluationRuntime.SERVER: + ... +``` + +
+Category + +Feature flags + +
+ +### `Client.get_feature_flag_keys_by_evaluation_runtime` + +```python +get_feature_flag_keys_by_evaluation_runtime(evaluation_runtime: Union[FeatureFlagEvaluationRuntime, str]) -> list[str] +``` + +Return the keys of locally loaded flags that a runtime can evaluate. + +A flag set to `FeatureFlagEvaluationRuntime.ALL` suits either runtime, +so it is returned for `CLIENT` and for `SERVER`, and asking for +`ALL` returns every loaded flag. Use this to decide which flags to hand +to a browser when a backend serves flags to its own frontend. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`evaluation_runtime` | Union[FeatureFlagEvaluationRuntime, str] | The runtime to match, as a `FeatureFlagEvaluationRuntime` or its string value. | *required* + +**Returns:** + +Type | Description +---- | ----------- +list[str] | The matching flag keys, in the order local evaluation loaded them. +list[str] | Empty when no definitions are loaded. + +**Examples:** + +```python +from posthog import FeatureFlagEvaluationRuntime + +client_keys = posthog.get_feature_flag_keys_by_evaluation_runtime( + FeatureFlagEvaluationRuntime.CLIENT +) +``` + +
+Category + +Feature flags + +
+ +### `posthog.integrations.django.PosthogContextMiddleware` + +```python +PosthogContextMiddleware(get_response) +``` + +Middleware to automatically track Django requests. + +This middleware wraps all calls with a posthog context. It attempts to extract the following from the request: +- Session ID, (extracted from `X-POSTHOG-SESSION-ID`) +- Distinct ID, (extracted from `X-POSTHOG-DISTINCT-ID`, falling back to the authenticated request user ID) +- Authenticated user email as `email` +- Request URL as `$current_url` +- Request method as `$request_method` +- Request path as `$request_path` +- Forwarded IP address as `$ip` +- User agent as `$user_agent` + +Configure the integration with these Django settings: +- `POSTHOG_MW_CLIENT`: client used for automatic exception capture, or the global client. + This does not redirect events explicitly captured through other clients. +- `POSTHOG_MW_CAPTURE_EXCEPTIONS`: `False` enables context enrichment only; + `True` enables automatic exception capture; `None` inherits the effective client's + `enable_exception_autocapture`. When omitted, the legacy default is `True`. +- `POSTHOG_MW_REQUEST_FILTER`: a callback receiving the request. Returning `False` + disables both enrichment and automatic exception capture for that request. +- `POSTHOG_MW_EXTRA_PROPERTIES`: a callback returning additional event properties. +- `POSTHOG_MW_CAPTURE_TRACING_HEADERS`: read PostHog identity and session headers + as request-scoped analytics context (default: `True`). Explicit capture IDs and + event properties take precedence. These values are never authentication or + authorization. Authenticated Django user identity remains the fallback when no + usable header ID is available or header capture is disabled. +- `POSTHOG_MW_TRUST_TRACING_HEADERS`: deprecated alias for + `POSTHOG_MW_CAPTURE_TRACING_HEADERS`; the capture-named setting takes precedence. +- `POSTHOG_MW_PROPERTIES_MAP`: an optional callback transforming request properties. + +The old `POSTHOG_MW_EXTRA_TAGS` and `POSTHOG_MW_TAG_MAP` settings remain aliases; +the property-named settings take precedence when both are configured. + +Request properties are included on all events captured within the active context, +including when automatic exception capture is disabled. +See the context documentation for more information. The extracted distinct ID and session ID, +if found, are used to associate all events captured in the middleware context with the same distinct ID +and session as currently active on the frontend. See the documentation for `set_context_session` +and `identify_context` for more details. + +This middleware is hybrid-capable: it supports both WSGI (sync) and ASGI (async) Django applications. The middleware +detects at initialization whether the next middleware in the chain is async or sync, and adapts its behavior accordingly. +This ensures compatibility with both pure sync and pure async middleware chains, as well as mixed chains in ASGI mode. + +**Functions:** + +Name | Description +---- | ----------- +`aextract_properties` | Async version of extract_properties for use in async request handling. +`aextract_request_user` | Async version of extract_request_user for use in async request handling. +`aextract_tags` | Compatibility alias for `aextract_properties`. +`extract_properties` | Extract event properties and identity from a synchronous request. +`extract_request_user` | Extract user ID and email from request in sync context. +`extract_tags` | Compatibility alias for `extract_properties`. +`process_exception` | Process exceptions from views and downstream middleware. + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`async_capable` | | +`capture_exceptions` | Optional[bool] | +`capture_tracing_headers` | | +`client` | | +`extra_properties` | | +`extra_tags` | | Compatibility alias for `extra_properties`. +`get_response` | | +`properties_map` | | +`request_filter` | | +`sync_capable` | | +`tag_map` | | Compatibility alias for `properties_map`. +`trust_tracing_headers` | bool | Deprecated alias for `capture_tracing_headers`. + +Initialize the middleware with Django's next handler. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`get_response` | | The next middleware or view handler in Django's middleware chain. May be synchronous or asynchronous. | + +#### `posthog.integrations.django.PosthogContextMiddleware.aextract_properties` + +```python +aextract_properties(request) +``` + +Async version of extract_properties for use in async request handling. + +Uses await request.auser() instead of request.user to avoid +SynchronousOnlyOperation in async context. + +Follows Django's naming convention for async methods (auser, asave, etc.). + +#### `posthog.integrations.django.PosthogContextMiddleware.aextract_request_user` + +```python +aextract_request_user(request) +``` + +Async version of extract_request_user for use in async request handling. + +Uses await request.auser() instead of request.user to avoid +SynchronousOnlyOperation in async context. + +Follows Django's naming convention for async methods (auser, asave, etc.). + +#### `posthog.integrations.django.PosthogContextMiddleware.aextract_tags` + +```python +aextract_tags(request) +``` + +Compatibility alias for `aextract_properties`. + +#### `posthog.integrations.django.PosthogContextMiddleware.async_capable` + +```python +async_capable = True +``` + +#### `posthog.integrations.django.PosthogContextMiddleware.capture_exceptions` + +```python +capture_exceptions: Optional[bool] = capture_exceptions if (capture_exceptions is None or isinstance(capture_exceptions, bool)) else True +``` + +#### `posthog.integrations.django.PosthogContextMiddleware.capture_tracing_headers` + +```python +capture_tracing_headers = _resolve_capture_tracing_headers(capture_tracing_headers, trust_tracing_headers, canonical_name='POSTHOG_MW_CAPTURE_TRACING_HEADERS', deprecated_name='POSTHOG_MW_TRUST_TRACING_HEADERS') +``` + +#### `posthog.integrations.django.PosthogContextMiddleware.client` + +```python +client = cast('Optional[Client]', settings.POSTHOG_MW_CLIENT) +``` + +#### `posthog.integrations.django.PosthogContextMiddleware.extra_properties` + +```python +extra_properties = cast('Callable[[HttpRequest], Dict[str, Any]]', extra_properties) if callable(extra_properties) else None +``` + +#### `posthog.integrations.django.PosthogContextMiddleware.extra_tags` + +```python +extra_tags +``` + +Compatibility alias for `extra_properties`. + +#### `posthog.integrations.django.PosthogContextMiddleware.extract_properties` + +```python +extract_properties(request) +``` + +Extract event properties and identity from a synchronous request. + +#### `posthog.integrations.django.PosthogContextMiddleware.extract_request_user` + +```python +extract_request_user(request) +``` + +Extract user ID and email from request in sync context. + +#### `posthog.integrations.django.PosthogContextMiddleware.extract_tags` + +```python +extract_tags(request) +``` + +Compatibility alias for `extract_properties`. + +#### `posthog.integrations.django.PosthogContextMiddleware.get_response` + +```python +get_response = get_response +``` + +#### `posthog.integrations.django.PosthogContextMiddleware.process_exception` + +```python +process_exception(request, exception) +``` + +Process exceptions from views and downstream middleware. + +Django calls this WHILE still inside the context created by __call__, +so request tags have already been extracted and set. This method just +needs to capture the exception directly. + +Django converts view exceptions into responses before they propagate through +the middleware stack, so the context manager in __call__/__acall__ never sees them. + +Note: Django's process_exception is always synchronous, even for async views. + +#### `posthog.integrations.django.PosthogContextMiddleware.properties_map` + +```python +properties_map = cast('Callable[[Dict[str, Any]], Dict[str, Any]]', properties_map) if callable(properties_map) else None +``` + +#### `posthog.integrations.django.PosthogContextMiddleware.request_filter` + +```python +request_filter = cast('Optional[Callable[[HttpRequest], bool]]', settings.POSTHOG_MW_REQUEST_FILTER) +``` + +#### `posthog.integrations.django.PosthogContextMiddleware.sync_capable` + +```python +sync_capable = True +``` + +#### `posthog.integrations.django.PosthogContextMiddleware.tag_map` + +```python +tag_map +``` + +Compatibility alias for `properties_map`. + +#### `posthog.integrations.django.PosthogContextMiddleware.trust_tracing_headers` + +```python +trust_tracing_headers: bool +``` + +Deprecated alias for `capture_tracing_headers`. + +### `posthog.integrations.flask.PosthogFlaskIntegration` + +```python +PosthogFlaskIntegration(app: Optional[Flask] = None, *, client: Optional[Client] = None, capture_exceptions: Optional[bool] = None, request_filter: Optional[Callable[[Request], bool]] = None, extra_properties: Optional[Callable[[Request], Mapping[str, Any]]] = None, trust_tracing_headers: Optional[bool] = None, capture_tracing_headers: Optional[bool] = None) -> None +``` + +Add PostHog request context and exception tracking to a Flask app. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`app` | Optional[Flask] | An optional Flask application. If omitted, call `init_app` later (the Flask application-factory pattern). | None +`client` | Optional[Client] | Optional destination for automatically captured exceptions. The global client is used by default. Request context still enriches events captured through any client and does not redirect capture calls. | None +`capture_exceptions` | Optional[bool] | Capture exceptions that reach Flask's unhandled exception machinery. If omitted, inherit the effective client's `enable_exception_autocapture` setting. Pass `True` or `False` to override it. | None +`request_filter` | Optional[Callable[[Request], bool]] | Optional callback receiving Flask's request object. A false return value disables both context and exception capture for that request. | None +`extra_properties` | Optional[Callable[[Request], Mapping[str, Any]]] | Optional callback returning additional event properties. This is useful for application-specific metadata such as an authenticated user's role. Values should not contain secrets or request bodies. | None +`capture_tracing_headers` | Optional[bool] | Read PostHog distinct and session ID headers as request-scoped analytics context. Defaults to True. Explicit capture IDs and event properties take precedence. Never use these values for security. | None +`trust_tracing_headers` | Optional[bool] | Deprecated alias for `capture_tracing_headers`. The capture-named option takes precedence when both are supplied. | None + +The integration intentionally does not capture exceptions handled by an +application error handler, expected HTTP exceptions, request or response +bodies, query strings, cookies, authorization headers, or arbitrary headers. +Call `capture_exception` explicitly from a custom error handler if a +handled exception should be reported. + +**Functions:** + +Name | Description +---- | ----------- +`init_app` | Register the integration with a Flask application once. + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`capture_exceptions` | | +`capture_tracing_headers` | | +`client` | | +`extra_properties` | | +`request_filter` | | +`trust_tracing_headers` | bool | Deprecated alias for `capture_tracing_headers`. + +#### `posthog.integrations.flask.PosthogFlaskIntegration.capture_exceptions` + +```python +capture_exceptions = capture_exceptions +``` + +#### `posthog.integrations.flask.PosthogFlaskIntegration.capture_tracing_headers` + +```python +capture_tracing_headers = _resolve_capture_tracing_headers(capture_tracing_headers, trust_tracing_headers) +``` + +#### `posthog.integrations.flask.PosthogFlaskIntegration.client` + +```python +client = client +``` + +#### `posthog.integrations.flask.PosthogFlaskIntegration.extra_properties` + +```python +extra_properties = extra_properties +``` + +#### `posthog.integrations.flask.PosthogFlaskIntegration.init_app` + +```python +init_app(app: Flask) -> None +``` + +Register the integration with a Flask application once. + +#### `posthog.integrations.flask.PosthogFlaskIntegration.request_filter` + +```python +request_filter = request_filter +``` + +#### `posthog.integrations.flask.PosthogFlaskIntegration.trust_tracing_headers` + +```python +trust_tracing_headers: bool +``` + +Deprecated alias for `capture_tracing_headers`. + +### `posthog.integrations.asgi.PosthogASGIMiddleware` + +```python +PosthogASGIMiddleware(app: _ASGIApp, client: Optional[Client] = None, capture_exceptions: Optional[bool] = None, request_filter: Optional[_RequestFilter] = None, extra_properties: Optional[_ExtraProperties] = None, trust_tracing_headers: Optional[bool] = None, *, capture_tracing_headers: Optional[bool] = None) -> None +``` + +Add PostHog context and automatic exception capture to an ASGI app. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`app` | _ASGIApp | The downstream ASGI application. | *required* +`client` | Optional[Client] | Optional destination for automatically captured exceptions. The global client is used by default. Request context still enriches events captured through any client and does not redirect capture calls. | None +`capture_exceptions` | Optional[bool] | Capture exceptions escaping the downstream app. If omitted, inherit the effective client's `enable_exception_autocapture` setting. Pass `True` or `False` to override it. | None +`request_filter` | Optional[_RequestFilter] | Optional sync or async callback receiving the ASGI scope. Returning `False` bypasses all instrumentation for that scope. | None +`extra_properties` | Optional[_ExtraProperties] | Optional sync or async callback receiving the ASGI scope and returning additional event properties. | None +`capture_tracing_headers` | Optional[bool] | Read PostHog distinct and session ID headers as request-scoped analytics context. Defaults to True. Explicit capture IDs and event properties take precedence. Never use these values for security. | None +`trust_tracing_headers` | Optional[bool] | Deprecated alias for `capture_tracing_headers`. The capture-named option takes precedence when both are supplied. | None + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`app` | | +`capture_exceptions` | | +`capture_tracing_headers` | | +`client` | | +`extra_properties` | | +`request_filter` | | +`trust_tracing_headers` | bool | Deprecated alias for `capture_tracing_headers`. + +#### `posthog.integrations.asgi.PosthogASGIMiddleware.app` + +```python +app = app +``` + +#### `posthog.integrations.asgi.PosthogASGIMiddleware.capture_exceptions` + +```python +capture_exceptions = capture_exceptions +``` + +#### `posthog.integrations.asgi.PosthogASGIMiddleware.capture_tracing_headers` + +```python +capture_tracing_headers = _resolve_capture_tracing_headers(capture_tracing_headers, trust_tracing_headers) +``` + +#### `posthog.integrations.asgi.PosthogASGIMiddleware.client` + +```python +client = client +``` + +#### `posthog.integrations.asgi.PosthogASGIMiddleware.extra_properties` + +```python +extra_properties = extra_properties +``` + +#### `posthog.integrations.asgi.PosthogASGIMiddleware.request_filter` + +```python +request_filter = request_filter +``` + +#### `posthog.integrations.asgi.PosthogASGIMiddleware.trust_tracing_headers` + +```python +trust_tracing_headers: bool +``` + +Deprecated alias for `capture_tracing_headers`. + +### `posthog.integrations.celery.PosthogCeleryIntegration` + +```python +PosthogCeleryIntegration(client: Optional[Client] = None, capture_exceptions: bool = True, capture_task_lifecycle_events: bool = True, propagate_context: bool = True, task_filter: Optional[Callable[[Optional[str], dict[str, Any]], bool]] = None) +``` + +Celery integration that captures task lifecycle events and exceptions. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`client` | Optional[Client] | Optional `Client` instance. When provided, all events and exceptions are captured through this client rather than the global `posthog` module. Don't skip this if using a custom flag definition cache provider, and pass the custom `Client` instance here initialized with the custom provider so fork safety for that provider is handled correctly. | None +`capture_exceptions` | bool | Whether to capture task exceptions via `capture_exception` (default `True`). | True +`capture_task_lifecycle_events` | bool | Whether to emit lifecycle events of the task such as "started", "success", "failure" etc. (default `True`). | True +`propagate_context` | bool | Whether to propagate PostHog context (distinct ID, session ID, tags) from the producer to the worker via task headers (default `True`). | True +`task_filter` | Optional[Callable[[Optional[str], dict[str, Any]], bool]] | Optional callback `(task_name, task_properties) -> bool` expected to return `False` if a given task should not be tracked. | None + +**Functions:** + +Name | Description +---- | ----------- +`instrument` | Connect Celery signal handlers to capture task events and exceptions. +`shutdown` | Disconnect all signal handlers registered by `instrument()`, flush all pending events +`uninstrument` | Disconnect Celery signal handlers and unregister exit cleanup. + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`capture_exceptions` | | +`capture_task_lifecycle_events` | | +`client` | | +`propagate_context` | | +`task_filter` | | + +#### `posthog.integrations.celery.PosthogCeleryIntegration.capture_exceptions` + +```python +capture_exceptions = capture_exceptions +``` + +#### `posthog.integrations.celery.PosthogCeleryIntegration.capture_task_lifecycle_events` + +```python +capture_task_lifecycle_events = capture_task_lifecycle_events +``` + +#### `posthog.integrations.celery.PosthogCeleryIntegration.client` + +```python +client = client +``` + +#### `posthog.integrations.celery.PosthogCeleryIntegration.instrument` + +```python +instrument() -> None +``` + +Connect Celery signal handlers to capture task events and exceptions. +Call this after initializing the PostHog client and this integration. + +If Celery runs on a single host, reinstrumenting in worker children is +not strictly necessary because the PostHog client and this integration +are fork-safe. If Celery workers run across multiple hosts, each worker +process must initialize PostHog, this integration, and call +`instrument()`. Celery provides `worker_process_init` signal to help +with this. + +#### `posthog.integrations.celery.PosthogCeleryIntegration.propagate_context` + +```python +propagate_context = propagate_context +``` + +#### `posthog.integrations.celery.PosthogCeleryIntegration.shutdown` + +```python +shutdown() -> None +``` + +Disconnect all signal handlers registered by `instrument()`, flush all pending events +and cleanly shutdown the integration. + +`shutdown()` is also registered on `worker_process_shutdown` and `atexit` signals, +but there is no guarantee those will always be called, so we strongly recommend calling +it manually when the integration is no longer needed to avoid data loss. + +#### `posthog.integrations.celery.PosthogCeleryIntegration.task_filter` + +```python +task_filter = task_filter +``` + +#### `posthog.integrations.celery.PosthogCeleryIntegration.uninstrument` + +```python +uninstrument() -> None +``` + +Disconnect Celery signal handlers and unregister exit cleanup. + +Do not use directly, call `shutdown()` instead. + +### `posthog.request.set_socket_options` + +```python +set_socket_options(socket_options: Optional[SocketOptions]) -> None +``` + +Configure socket options for all SDK HTTP connections. + +Call this during initialization, before making API requests. Pass `None` +to reset to the default socket behavior. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`socket_options` | Optional[SocketOptions] | A list of `(level, option, value)` tuples accepted by urllib3/`socket.setsockopt()`, or `None` to reset defaults. | *required* + +
+Example + +from posthog import set_socket_options +set_socket_options([(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)]) + +
+ +### `posthog.request.enable_keep_alive` + +```python +enable_keep_alive() -> None +``` + +Enable TCP keepalive for SDK HTTP connections. + +This helps prevent idle pooled connections from being dropped by network +infrastructure. Call during initialization, before making API requests. + +### `posthog.request.disable_connection_reuse` + +```python +disable_connection_reuse() -> None +``` + +Disable HTTP connection reuse for SDK requests. + +Each request will create a fresh connection. This can avoid issues with +environments that terminate pooled connections, but adds per-request +overhead. Call during initialization, before making API requests. + +## AsyncClient + +Needs the `async` extra, `posthog[async]`. + +### `AsyncClient.alias` + +```python +alias(previous_id: ID_TYPES, distinct_id: Optional[str], timestamp: Optional[Union[datetime, str]] = None, uuid: Optional[str] = None, disable_geoip: Optional[bool] = None, options: Optional[Dict[str, Any]] = None) -> Optional[str] +``` + +### `AsyncClient.capture` + +```python +capture(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] +``` + +Queue an event without blocking for network delivery. + +### `AsyncClient.capture_immediate` + +```python +capture_immediate(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] +``` + +Capture one event and wait until its delivery attempt completes. + +### `AsyncClient.capture_ai` + +```python +capture_ai(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] +``` + +Queue an AI event for the AI capture endpoint without blocking. + +Takes the same arguments and returns the same value as `capture()`. +The event uses a separate queue with a larger per-event size cap. The +payload is sent as given, with no redaction or truncation. + +### `AsyncClient.capture_ai_immediate` + +```python +capture_ai_immediate(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] +``` + +Capture one AI event and wait until its delivery attempt completes. + +Uses the AI capture endpoint and the `capture_ai_*` settings. + +### `AsyncClient.capture_exception` + +```python +capture_exception(exception: Optional[ExceptionArg] = None, *, level: Optional[str] = None, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] +``` + +Capture an exception. This method never raises, including in debug mode. + +`level` sets `$exception_level` and defaults to `"error"`. An unknown +value counts as unset. Reserved exception properties in `properties`, +such as `$exception_list` and `$exception_level`, are ignored. + +When OpenTelemetry is installed and a valid span is active, its trace and +span IDs are added as `$trace_id` and `$span_id` event properties, +unless the event, a context tag or `super_properties` sets either one. + +### `AsyncClient.emails` + +```python +emails: AsyncEmails +``` + +Send transactional email from PostHog email templates, and read the +status of each email sent with `get`. + +Needs `secret_key`, a project secret API key with the `hog_flow:write` +scope, and `posthog[async]`. Sends go to `app_host`, or to the PostHog +Cloud app host of `host`. `email_request_timeout_seconds` and +`email_request_max_retries` configure each send. A disabled or shut-down +client sends nothing. See `posthog.emails` for setup. + +**Examples:** + +```python +await client.emails.send( + to="user@example.com", + template_id="0190f6d2-7c1e-7b3a-9f5e-1c2d3e4f5a6b", + distinct_id="user_123", +) +``` + +### `AsyncClient.flush` + +```python +flush(timeout_seconds: Optional[float] = 10) -> None +``` + +### `AsyncClient.evaluate_flags` + +```python +evaluate_flags(distinct_id: Optional[ID_TYPES] = None, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, disable_geoip: Optional[bool] = None, flag_keys: Optional[list[str]] = None, device_id: Optional[str] = None) -> FeatureFlagEvaluations +``` + +Evaluate flags remotely without blocking the event loop. + +The returned snapshot has synchronous in-memory accessors. Pass it to +`capture(flags=...)` to attach the exact values used for branching. +This client does not evaluate flags locally, so the snapshot's +`unresolved_flags` is always empty. + +### `AsyncClient.group_identify` + +```python +group_identify(group_type: str, group_key: str, properties: Optional[Dict[str, Any]] = None, timestamp: Optional[Union[datetime, str]] = None, uuid: Optional[Union[str, UUID]] = None, disable_geoip: Optional[bool] = None, distinct_id: Optional[ID_TYPES] = None, options: Optional[Dict[str, Any]] = None) -> Optional[str] +``` + +### `AsyncClient.set` + +```python +set(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str] +``` + +### `AsyncClient.set_once` + +```python +set_once(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str] +``` + +### `AsyncClient(...)` + +```python +__init__(project_api_key: str, host: Optional[str] = None, *, debug: bool = False, max_queue_size: int = 10000, send: bool = True, on_error: bool = None, flush_at: int = 100, flush_interval: float = 5.0, max_retries: int = 3, timeout: float = 15, thread: int = 1, disabled: bool = False, disable_geoip: bool = True, is_server: bool = True, historical_migration: bool = False, super_properties: Optional[dict[str, Any]] = None, super_options: Optional[dict[str, Any]] = None, before_send: Optional[dict[str, Any]] = None, log_captured_exceptions: bool = False, project_root: Optional[str] = None, capture_exception_code_variables: bool = False, code_variables_mask_patterns: bool = None, code_variables_ignore_patterns: bool = None, code_variables_mask_url_credentials: bool = None, code_variables_detect_secrets: bool = None, in_app_modules: Optional[list[str]] = None, capture_compression: Optional[Union[CaptureCompression, str]] = None, capture_ai_compression: Optional[Union[CaptureCompression, str]] = None, capture_ai_max_queue_size: int = 1000, capture_ai_timeout: float = 30, capture_ai_max_event_bytes: int = AI_MAX_MSG_SIZE, privacy_mode: bool = False, enable_full_ai_capture: bool = False, capture_trace_context: bool = False, secret_key: Optional[str] = None, personal_api_key: Optional[str] = None, feature_flags_request_timeout_seconds: float = 3, feature_flags_request_max_retries: int = 1, app_host: Optional[str] = None, email_request_timeout_seconds: float = 15, email_request_max_retries: int = 2) -> None +``` + +### `AsyncClient.shutdown` + +```python +shutdown() -> None +``` + +### `AsyncClient.join` + +```python +join() -> None +``` + +### `AsyncClient.get_remote_config_payload` + +```python +get_remote_config_payload(key: str) -> Optional[Any] +``` + +Fetch and decrypt a remote-config payload without blocking the loop. + +## Module functions + +### `posthog.new_context` + +```python +new_context(fresh: bool = False, capture_exceptions: Optional[bool] = None, client: Optional[Client] = None) +``` + +Create a new context scope that will be active for the duration of the with block. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`fresh` | bool | Whether to start with a fresh context (default: False) | False +`capture_exceptions` | Optional[bool] | Whether to capture exceptions raised within the context. If omitted, defaults to the relevant client's exception autocapture setting. | None +`client` | Optional[Client] | Optional Posthog client instance to use for this context (default: None) | None + +**Examples:** + +```python +from posthog import new_context, tag, capture +with new_context(): + tag("request_id", "123") + capture("event_name", properties={"property": "value"}) +``` + +
+Category + +Contexts + +
+ +### `posthog.scoped` + +```python +scoped(fresh = False, capture_exceptions: Optional[bool] = None) +``` + +Decorator that creates a new context for the function. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`fresh` | | Whether to start with a fresh context (default: False) | False +`capture_exceptions` | Optional[bool] | Whether to capture and track exceptions with posthog error tracking. If omitted, defaults to the global exception autocapture setting. | None + +**Examples:** + +```python +from posthog import scoped, tag, capture +@scoped() +def process_payment(payment_id): + tag("payment_id", payment_id) + capture("payment_started") +``` + +
+Category + +Contexts + +
+ +### `posthog.set_context_session` + +```python +set_context_session(session_id: str) +``` + +Set the session ID for the current context. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`session_id` | str | The session ID to associate with the current context and its children | *required* + +**Examples:** + +```python +from posthog import set_context_session +set_context_session("session_123") +``` + +
+Category + +Contexts + +
+ +### `posthog.set_context_device_id` + +```python +set_context_device_id(device_id: str) +``` + +Set the device ID for the current context, associating all feature flag requests +in this or child contexts with the given device ID. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`device_id` | str | The device ID to associate with the current context and its children | *required* + +**Examples:** + +```python +from posthog import set_context_device_id +set_context_device_id("device_123") +``` + +
+Category + +Contexts + +
+ +### `posthog.identify_context` + +```python +identify_context(distinct_id: str) +``` + +Identify the current context with a distinct ID. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | str | The distinct ID to associate with the current context and its children | *required* + +**Examples:** + +```python +from posthog import identify_context +identify_context("user_123") +``` + +
+Category + +Identification + +
+ +### `posthog.set_capture_exception_code_variables_context` + +```python +set_capture_exception_code_variables_context(enabled: bool) +``` + +Override code-variable capture for exceptions in the current context. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`enabled` | bool | Whether exceptions captured in this context should include local variable values from stack frames. | *required* + +
+Category + +Contexts + +
+ +### `posthog.set_code_variables_mask_patterns_context` + +```python +set_code_variables_mask_patterns_context(mask_patterns: list[str]) +``` + +Override code-variable mask patterns for exceptions in the current context. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`mask_patterns` | list[str] | Variable-name patterns whose values should be replaced with `***` when code variables are captured. | *required* + +
+Category + +Contexts + +
+ +### `posthog.set_code_variables_ignore_patterns_context` + +```python +set_code_variables_ignore_patterns_context(ignore_patterns: list[str]) +``` + +Override code-variable ignore patterns for exceptions in the current context. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`ignore_patterns` | list[str] | Variable-name patterns that should be omitted entirely when code variables are captured. | *required* + +
+Category + +Contexts + +
+ +### `posthog.set_code_variables_mask_url_credentials_context` + +```python +set_code_variables_mask_url_credentials_context(enabled: bool) +``` + +Whether to scrub credentials embedded in URLs/DSNs (e.g. user:pass@host) from +captured code variables for the current context. + +### `posthog.set_code_variables_detect_secrets_context` + +```python +set_code_variables_detect_secrets_context(enabled: bool) +``` + +Whether to apply entropy-based secret detection as a last-resort redaction of +high-entropy values (API keys, tokens, strong passwords) in captured code +variables for the current context. + +
+Category + +Contexts + +
+ +### `posthog.tag` + +```python +tag(name: str, value: Any) +``` + +Add a tag to the current context. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`name` | str | The tag key | *required* +`value` | Any | The tag value | *required* + +**Examples:** + +```python +from posthog import tag +tag("user_id", "123") +``` + +
+Category + +Contexts + +
+ +### `posthog.get_tags` + +```python +get_tags() -> Dict[str, Any] +``` + +Get all tags from the current context. + +**Returns:** + +Type | Description +---- | ----------- +Dict[str, Any] | Dict of all tags in the current context + +
+Category + +Contexts + +
+ +### `posthog.set_context_option` + +```python +set_context_option(key: str, value: Any) -> None +``` + +Set a capture option for every event captured within the current context. + +Context options fill options an event leaves unset, before `before_send` +runs, so the hook sees them. They override `super_options`. An event's +own `options` override them, and `before_send` can change them. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The option name, such as `"process_person_profile"` | *required* +`value` | Any | The option value, sent as given | *required* + +**Examples:** + +```python +from posthog import new_context, set_context_option +with new_context(): + set_context_option("process_person_profile", False) +``` + +
+Category + +Contexts + +
+ +### `posthog.get_context_options` + +```python +get_context_options() -> Dict[str, Any] +``` + +Get all capture options from the current context. + +**Returns:** + +Type | Description +---- | ----------- +Dict[str, Any] | Dict of all capture options in the current context + +
+Category + +Contexts + +
+ +### `posthog.capture` + +```python +capture(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] +``` + +Capture anything a user does within your system. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`event` | str | The event name to specify the event | *required* +`**kwargs` | Unpack[OptionalCaptureArgs] | Optional arguments including: distinct_id: Unique identifier for the user properties: Dict of event properties timestamp: When the event occurred. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. uuid: Unique identifier for this event. If omitted, one is generated and returned. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. groups: Dict of group types and IDs flags: A FeatureFlagEvaluations snapshot from evaluate_flags(). The exact values from the snapshot are attached with no extra /flags request. send_feature_flags: Deprecated. Prefer flags=... from evaluate_flags(). When truthy, evaluates flags during capture and attaches them to the event. disable_geoip: Whether to disable GeoIP lookup | {} + +
+Details + +Capture allows you to capture anything a user does within your system, which you can later use in PostHog to find patterns in usage, work out which features to improve or where people are giving up. A capture call requires an event name to specify the event. We recommend using [verb] [noun], like `movie played` or `movie updated` to easily identify what your events mean later on. Capture takes a number of optional arguments, which are defined by the `OptionalCaptureArgs` type. + +
+ +**Examples:** + +```python +# Context and capture usage +from posthog import new_context, identify_context, tag_context, capture +# Enter a new context (e.g. a request/response cycle, an instance of a background job, etc) +with new_context(): + # Associate this context with some user, by distinct_id + identify_context('some user') + + # Capture an event, associated with the context-level distinct ID ('some user') + capture('movie started') + + # Capture an event associated with some other user (overriding the context-level distinct ID) + capture('movie joined', distinct_id='some-other-user') + + # Capture an event with some properties + capture('movie played', properties={'movie_id': '123', 'category': 'romcom'}) + + # Capture an event with some properties + capture('purchase', properties={'product_id': '123', 'category': 'romcom'}) + # Capture an event with some associated group + capture('purchase', groups={'company': 'id:5'}) + + # Adding a tag to the current context will cause it to appear on all subsequent events + tag_context('some-tag', 'some-value') + + capture('another-event') # Will be captured with `'some-tag': 'some-value'` in the properties dict +``` +```python +# Set event properties +from posthog import capture +capture( + "user_signed_up", + distinct_id="distinct_id_of_the_user", + properties={ + "login_type": "email", + "is_free_trial": "true" + } +) +``` + +### `posthog.capture_ai` + +```python +capture_ai(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] +``` + +Capture an AI event on the dedicated AI capture endpoint. + +Beta: the signature is stable; operational limits (per-event size cap, +batching, endpoint) may change without notice. + +Takes the same arguments and returns the same value as `capture()`: the +event UUID, or None when the event was not admitted (disabled client, or +dropped by `before_send`). The event is delivered on an isolated queue +with its own consumer pool and a higher per-event size cap, posting to +the dedicated AI ingestion endpoint. The payload is sent as given — no +redaction or truncation is applied here. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`event` | str | The event name, normally one of the `$ai_*` event names. | *required* +`**kwargs` | Unpack[OptionalCaptureArgs] | Same optional arguments as `capture()`. | {} + +**Examples:** + +```python +from posthog import capture_ai + +uuid = capture_ai( + "$ai_generation", + distinct_id="user_123", + properties={"$ai_model": "gpt-5"}, +) +``` + +### `posthog.set` + +```python +set(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str] +``` + +Set properties on a user record. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`**kwargs` | Unpack[OptionalSetArgs] | Optional arguments including: distinct_id: Unique identifier for the user. Falls back to the context distinct ID; if none exists, this call does nothing. properties: Dict of person properties to set. timestamp: When the properties were set. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. uuid: Unique identifier for this operation. If omitted, one is generated and returned. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. disable_geoip: Whether to disable GeoIP lookup. | {} + +
+Details + +This will overwrite previous people property values. Generally operates similar to `capture`, with distinct_id being an optional argument, defaulting to the current context's distinct ID. If there is no context-level distinct ID, and no override distinct_id is passed, this function will do nothing. Context tags are folded into $set properties, so tagging the current context and then calling `set` will cause those tags to be set on the user (unlike capture, which causes them to just be set on the event). + +
+ +**Examples:** + +```python +# Set person properties +from posthog import set +set(distinct_id='distinct_id', properties={'name': 'Max Hedgehog'}) +``` + +### `posthog.set_once` + +```python +set_once(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str] +``` + +Set properties on a user record, only if they do not yet exist. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`**kwargs` | Unpack[OptionalSetArgs] | Optional arguments including: distinct_id: Unique identifier for the user. Falls back to the context distinct ID; if none exists, this call does nothing. properties: Dict of person properties to set only once. timestamp: When the properties were set. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. uuid: Unique identifier for this operation. If omitted, one is generated and returned. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. disable_geoip: Whether to disable GeoIP lookup. | {} + +
+Details + +This will not overwrite previous people property values, unlike `set`. Otherwise, operates in an identical manner to `set`. + +
+ +**Examples:** + +```python +# Set property once +from posthog import set_once +set_once(distinct_id='distinct_id', properties={'initial_url': '/blog'}) + +``` + +### `posthog.group_identify` + +```python +group_identify(group_type: str, group_key: str, properties: Optional[Dict[str, Any]] = None, timestamp: Optional[Union[datetime.datetime, str]] = None, uuid: Optional[str] = None, disable_geoip: Optional[bool] = None, distinct_id: Optional[ID_TYPES] = None, options: Optional[Dict[str, Any]] = None) -> Optional[str] +``` + +Set properties on a group. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`group_type` | str | Type of your group. Required - the call is dropped with a warning if it is missing or empty. | *required* +`group_key` | str | Unique identifier of the group. Required - the call is dropped with a warning if it is missing or empty. | *required* +`properties` | Optional[Dict[str, Any]] | Properties to set on the group | None +`timestamp` | Optional[Union[datetime, str]] | Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | None +`uuid` | Optional[str] | Optional UUID for the event | None +`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup | None +`distinct_id` | Optional[ID_TYPES] | Optional distinct ID of the user performing the action | None +`options` | Optional[Dict[str, Any]] | Optional capture options for the event, sent as given | None + +**Examples:** + +```python +# Group identify +from posthog import group_identify +group_identify('company', 'company_id_in_your_db', { + 'name': 'Awesome Inc.', + 'employees': 11 +}) +``` + +### `posthog.alias` + +```python +alias(previous_id: ID_TYPES, distinct_id: str, timestamp: Optional[Union[datetime.datetime, str]] = None, uuid: Optional[str] = None, disable_geoip: Optional[bool] = None, options: Optional[Dict[str, Any]] = None) -> Optional[str] +``` + +Associate user behaviour before and after they e.g. register, login, or perform some other identifying action. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`previous_id` | ID_TYPES | The unique ID of the user before | *required* +`distinct_id` | str | The current unique id | *required* +`timestamp` | Optional[Union[datetime, str]] | Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | None +`uuid` | Optional[str] | Optional UUID for the event | None +`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup | None +`options` | Optional[Dict[str, Any]] | Optional capture options for the event, sent as given | None + +
+Details + +To marry up whatever a user does before they sign up or log in with what they do after you need to make an alias call. This will allow you to answer questions like "Which marketing channels leads to users churning after a month?" or "What do users do on our website before signing up?". Particularly useful for associating user behaviour before and after they e.g. register, login, or perform some other identifying action. + +
+ +**Examples:** + +```python +# Alias user +from posthog import alias +alias(previous_id='distinct_id', distinct_id='alias_id') +``` + +### `posthog.capture_exception` + +```python +capture_exception(exception: Optional[ExceptionArg] = None, *, level: Optional[str] = None, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] +``` + +Capture exceptions that happen in your code. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`exception` | Optional[ExceptionArg] | The exception to capture. If not provided, the current exception is captured via `sys.exc_info()` | None +`level` | Optional[str] | The `$exception_level`, such as `"warning"` or `"fatal"`. Defaults to `"error"`. An unknown value counts as unset. | None +`**kwargs` | Unpack[OptionalCaptureArgs] | Optional capture arguments including distinct_id, properties, timestamp, uuid, groups, flags, send_feature_flags, disable_geoip, and options. Reserved exception properties in `properties`, such as `$exception_list` and `$exception_level`, are ignored. | {} + +
+Details + +Capture exception is idempotent - if it is called twice with the same exception instance, only a occurrence will be tracked in posthog. This is because, generally, contexts will cause exceptions to be captured automatically. However, to ensure you track an exception, if you catch and do not re-raise it, capturing it manually is recommended, unless you are certain it will have crossed a context boundary (e.g. by existing a `with posthog.new_context():` block already). If the passed exception was raised and caught, the captured stack trace will consist of every frame between where the exception was raised and the point at which it is captured (the "traceback"). If the passed exception was never raised, e.g. if you call `posthog.capture_exception(ValueError("Some Error"))`, the stack trace captured will be the full stack trace at the moment the exception was captured. Note that heavy use of contexts will lead to truncated stack traces, as the exception will be captured by the context entered most recently, which may not be the point you catch the exception for the final time in your code. It's recommended to use contexts sparingly, for this reason. `capture_exception` takes the same set of optional arguments as `capture`. + +
+ +**Examples:** + +```python +# Capture exception +from posthog import capture_exception +try: + risky_operation() +except Exception as e: + capture_exception(e) +``` + +### `posthog.feature_enabled` + +```python +feature_enabled(key: str, distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[bool] +``` + +Use feature flags to enable or disable features for users. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The feature flag key | *required* +`distinct_id` | ID_TYPES | The user's distinct ID | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | Groups mapping | None +`person_properties` | Optional[Dict[str, Any]] | Person properties | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties | None +`only_evaluate_locally` | bool | Whether to evaluate only locally | False +`send_feature_flag_events` | bool | Whether to send feature flag events | True +`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup | None +`device_id` | Optional[str] | Optional device ID override for experience-continuity flags | None + +
+Details + +You can call `posthog.load_feature_flags()` before to make sure you're not doing unexpected requests. + +
+ +**Examples:** + +```python +# Boolean feature flag +from posthog import feature_enabled, get_feature_flag_payload +is_my_flag_enabled = feature_enabled('flag-key', 'distinct_id_of_your_user') +if is_my_flag_enabled: + matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user') +``` + +### `posthog.get_feature_flag` + +```python +get_feature_flag(key: str, distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[FlagValue] +``` + +Get feature flag variant for users. Used with experiments. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The feature flag key | *required* +`distinct_id` | ID_TYPES | The user's distinct ID | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | Groups mapping from group type to group key | None +`person_properties` | Optional[Dict[str, Any]] | Person properties | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties in format { group_type_name: { group_properties } } | None +`only_evaluate_locally` | bool | Whether to evaluate only locally | False +`send_feature_flag_events` | bool | Whether to send feature flag events | True +`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup | None +`device_id` | Optional[str] | Optional device ID override for experience-continuity flags | None + +
+Details + +`groups` are a mapping from group type to group key. So, if you have a group type of "organization" and a group key of "5", you would pass groups={"organization": "5"}. `group_properties` take the format: { group_type_name: { group_properties } }. So, for example, if you have the group type "organization" and the group key "5", with the properties name, and employee count, you'll send these as: group_properties={"organization": {"name": "PostHog", "employees": 11}}. + +
+ +**Examples:** + +```python +# Multivariate feature flag +from posthog import get_feature_flag, get_feature_flag_payload +enabled_variant = get_feature_flag('flag-key', 'distinct_id_of_your_user') +if enabled_variant == 'variant-key': + matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user') +``` + +### `posthog.get_all_flags` + +```python +get_all_flags(distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None, flag_keys_to_evaluate: Optional[list[str]] = None) -> Optional[dict[str, FlagValue]] +``` + +Get all flags for a given user. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | ID_TYPES | The user's distinct ID | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | Groups mapping | None +`person_properties` | Optional[Dict[str, Any]] | Person properties | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties | None +`only_evaluate_locally` | bool | Whether to evaluate only locally | False +`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup | None +`device_id` | Optional[str] | Optional device ID override for experience-continuity flags | None +`flag_keys_to_evaluate` | Optional[list[str]] | Optional list of flag keys to evaluate (evaluates all if None) | None + +
+Details + +Flags are key-value pairs where the key is the flag key and the value is the flag variant, or True, or False. + +
+ +**Examples:** + +```python +# All flags for user +from posthog import get_all_flags +get_all_flags('distinct_id_of_your_user') +``` + +### `posthog.get_feature_flag_result` + +```python +get_feature_flag_result(key: str, distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[FeatureFlagResult] +``` + +Get a FeatureFlagResult object which contains the flag result and payload. + +This method evaluates a feature flag and returns a FeatureFlagResult object containing: +- enabled: Whether the flag is enabled +- variant: The variant value if the flag has variants +- payload: The payload associated with the flag (automatically deserialized from JSON) +- key: The flag key +- reason: Why the flag was enabled/disabled + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The feature flag key. | *required* +`distinct_id` | ID_TYPES | The user's distinct ID. | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | Mapping of group type to group key. | None +`person_properties` | Optional[Dict[str, Any]] | Person properties to use for evaluation. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties keyed by group type. | None +`only_evaluate_locally` | bool | Whether to evaluate only locally. | False +`send_feature_flag_events` | bool | Whether to send a $feature_flag_called event. | True +`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup. | None +`device_id` | Optional[str] | Optional device ID override for experience-continuity flags. | None + +Example: +```python +result = posthog.get_feature_flag_result('beta-feature', 'distinct_id') +if result and result.enabled: + # Use the variant and payload + print(f"Variant: {result.variant}") + print(f"Payload: {result.payload}") +``` + +### `posthog.get_feature_flag_payload` + +```python +get_feature_flag_payload(key: str, distinct_id: ID_TYPES, match_value: Optional[FlagValue] = None, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[object] +``` + +Get the payload associated with a feature flag value. + +Deprecated for new code. Prefer `evaluate_flags()` and +`flags.get_flag_payload(key)` so flag evaluation happens once per request. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The feature flag key. | *required* +`distinct_id` | ID_TYPES | The user's distinct ID. | *required* +`match_value` | Optional[FlagValue] | Optional flag value to use when selecting a payload. | None +`groups` | Optional[Mapping[str, Union[str, int]]] | Mapping of group type to group key. | None +`person_properties` | Optional[Dict[str, Any]] | Person properties to use for evaluation. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties keyed by group type. | None +`only_evaluate_locally` | bool | Whether to evaluate only locally. | False +`send_feature_flag_events` | bool | Whether to send a $feature_flag_called event. | True +`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup. | None +`device_id` | Optional[str] | Optional device ID override for experience-continuity flags. | None + +**Returns:** + +Type | Description +---- | ----------- +Optional[object] | The payload associated with the matched feature flag value, or None. +Optional[object] | This function returns the payload only, not the FeatureFlagResult wrapper +Optional[object] | used internally to compute it. + +### `posthog.get_remote_config_payload` + +```python +get_remote_config_payload(key: str) +``` + +Get the payload for a remote config feature flag. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The key of the feature flag | *required* + +**Returns:** + +Type | Description +---- | ----------- + | The payload associated with the feature flag. If payload is encrypted, the return value will be decrypted + +
+Note + +Requires secret_key to be set for authentication + +
+ +### `posthog.get_all_flags_and_payloads` + +```python +get_all_flags_and_payloads(distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None, flag_keys_to_evaluate: Optional[list[str]] = None) -> FlagsAndPayloads +``` + +Get all feature flag values and payloads for a user. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | ID_TYPES | The user's distinct ID. | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | Mapping of group type to group key. | None +`person_properties` | Optional[Dict[str, Any]] | Person properties to use for evaluation. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties keyed by group type. | None +`only_evaluate_locally` | bool | Whether to evaluate only locally. | False +`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup. | None +`device_id` | Optional[str] | Optional device ID override for experience-continuity flags. | None +`flag_keys_to_evaluate` | Optional[list[str]] | Optional list of flag keys to evaluate. Evaluates all flags when omitted. | None + +**Returns:** + +Type | Description +---- | ----------- +FlagsAndPayloads | A dict with `featureFlags` and `featureFlagPayloads` entries. + +### `posthog.evaluate_flags` + +```python +evaluate_flags(distinct_id: Optional[ID_TYPES] = None, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, flag_keys: Optional[list[str]] = None, device_id: Optional[str] = None) -> FeatureFlagEvaluations +``` + +Evaluate all feature flags for a user in a single call and return a +`FeatureFlagEvaluations` snapshot. Branch on `.is_enabled()` / +`.get_flag()` and pass the same snapshot to `capture()` via the +`flags` option so events carry the exact flag values the code branched on. + +Prefer this over repeated `get_feature_flag()` calls and over +`capture(send_feature_flags=True)` — it consolidates flag evaluation into +a single `/flags` request per incoming request. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | Optional[ID_TYPES] | The user's distinct ID. If `None`, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot. | None +`groups` | Optional[Mapping[str, Union[str, int]]] | Mapping of group type to group key. | None +`person_properties` | Optional[Dict[str, Any]] | Person properties to use for evaluation. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties keyed by group type. | None +`only_evaluate_locally` | bool | If `True`, never fall back to remote evaluation and omit flags that cannot be evaluated locally. The snapshot's `unresolved_flags` lists them with a reason. | False +`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup. | None +`flag_keys` | Optional[list[str]] | Optional list that scopes local evaluation, the underlying `/flags` request, and the returned snapshot. When omitted or `None`, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per `evaluate_flags` call unless `only_evaluate_locally` is `True`. If the server also does not know the key, it is omitted from the snapshot. | None +`device_id` | Optional[str] | Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes. | None + +**Examples:** + +```python +from posthog import evaluate_flags, capture +flags = evaluate_flags("user_123", person_properties={"plan": "enterprise"}) +if flags.is_enabled("new-dashboard"): + render_new_dashboard() +capture("page_viewed", distinct_id="user_123", flags=flags) +``` + +### `posthog.feature_flag_definitions` + +```python +feature_flag_definitions() +``` + +Returns loaded feature flags. + +
+Details + +Returns loaded feature flags, if any. Helpful for debugging what flag information you have loaded. + +
+ +**Examples:** + +```python +from posthog import feature_flag_definitions +definitions = feature_flag_definitions() +``` + +### `posthog.get_feature_flag_evaluation_runtime` + +```python +get_feature_flag_evaluation_runtime(key: str) -> Optional[FeatureFlagEvaluationRuntime] +``` + +Return where a locally loaded feature flag is meant to be evaluated. + +
+Details + +Reads the `evaluation_runtime` each flag definition carries, so no extra +request is made. Returns `None` when local evaluation has not loaded a +definition for this key. A definition that carries no runtime reports +`FeatureFlagEvaluationRuntime.ALL`, the default PostHog applies. + +
+ +**Examples:** + +```python +from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_evaluation_runtime +runtime = get_feature_flag_evaluation_runtime("my-flag") +``` + +
+Category + +Feature flags + +
+ +### `posthog.get_feature_flag_keys_by_evaluation_runtime` + +```python +get_feature_flag_keys_by_evaluation_runtime(evaluation_runtime: Union[FeatureFlagEvaluationRuntime, str]) -> list[str] +``` + +Return the keys of locally loaded flags that a runtime can evaluate. + +
+Details + +A flag set to `FeatureFlagEvaluationRuntime.ALL` suits either runtime, so +it is returned for `CLIENT` and for `SERVER`. Use this to decide which +flags to hand to a browser when a backend serves flags to its own +frontend. + +
+ +**Examples:** + +```python +from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_keys_by_evaluation_runtime +client_keys = get_feature_flag_keys_by_evaluation_runtime(FeatureFlagEvaluationRuntime.CLIENT) +``` + +
+Category + +Feature flags + +
+ +### `posthog.load_feature_flags` + +```python +load_feature_flags() +``` + +Load feature flag definitions from PostHog. + +**Examples:** + +```python +from posthog import load_feature_flags +load_feature_flags() +``` + +### `posthog.flush` + +```python +flush(timeout_seconds: Optional[float] = 10) -> None +``` + +Attempt immediate delivery of queued events and ended spans, keeping the global client reusable. + +Call `flush()` before a short-lived request or serverless invocation returns +when using the shared module-level client. Use `shutdown()` only for final +process cleanup or a client that will not be reused. Flush does not stop the +background workers or feature flag poller, and does not end open spans. + +Returning does not guarantee server receipt: pending work can remain after a +timeout or span export failure, and failed events may be dropped by consumers. +Calls made directly from SDK callbacks such as `on_error` are deferred +rather than blocking the worker that invoked the callback. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`timeout_seconds` | Optional[float] | Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass `None` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently, waiting up to this many seconds for an in-flight flush before starting a fresh drain budget. A lock wait timeout leaves the in-flight flush to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. The last span request is not cut short and is bounded by the client's `timeout` in seconds, so this is not a strict wall-clock limit for the whole call. With `None`, a retriable span failure is left for a later flush rather than retried within this call. | 10 + +**Examples:** + +```python +import posthog +posthog.capture('event_name', distinct_id='user_id') +# Before a serverless invocation returns; keep the global client open. +posthog.flush(timeout_seconds=10) +``` + +### `posthog.join` + +```python +join() -> None +``` + +Attempt to process queued events and stop the client's background workers. Use `shutdown()` directly in most cases. + +Failed or undrainable events may be dropped and reported through logging or +`on_error`; returning does not guarantee server receipt. Lifecycle cleanup +is attempted once, and cleanup failures are logged without retry. + +**Examples:** + +```python +from posthog import join +join() +``` + +### `posthog.start_span` + +```python +start_span(name: str, *, kind: Optional[str] = None, attributes: Optional[Mapping[str, Any]] = None, parent: Union[Span, str, None] = None, tracestate: Optional[str] = None, start_time: Union[datetime.datetime, float, None] = None) -> Span +``` + +Start a span for distributed tracing. Alpha. + +Returns a span handle. Use it as a context manager to make it the active +span for the block and end it on exit (recording a raised exception on the +way out); or call `end()` yourself for a span that cannot wrap a block. +Spans started inside the block nest under it automatically. Always returns +a usable handle, even when tracing is off, so calling code never branches. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`name` | str | A low-cardinality operation name, e.g. `GET /users/:id`. Variable values belong in attributes, not the name. | *required* +`kind` | Optional[str] | `internal` (default), `server`, `client`, `producer` or `consumer`. | None +`attributes` | Optional[Mapping[str, Any]] | Initial attributes. | None +`parent` | Union[Span, str, None] | A span handle, or an inbound W3C `traceparent` header value to continue a remote trace. Defaults to the active span. | None +`tracestate` | Optional[str] | The inbound `tracestate` header accompanying a `traceparent` string `parent`; preserved and propagated. | None +`start_time` | Union[datetime, float, None] | A `datetime` or epoch seconds, to backdate the span. | None + +**Examples:** + +```python +import posthog +posthog.traces = {"service_name": "checkout-api"} + +with posthog.start_span("POST /checkout", parent=request.headers.get("traceparent")) as span: + span.set_attribute("plan", user.plan) + with posthog.start_span("db.query", kind="client"): + ... + outgoing_headers = {"traceparent": span.traceparent()} +``` + +### `posthog.get_active_span` + +```python +get_active_span() -> Optional[Span] +``` + +The span that is active in the current context, or `None`. Alpha. + +Only entering a span (`with posthog.start_span(...) as span:`) makes it +active; a span started manually is not. Use it to propagate the trace to +the next service: `span.traceparent()` is the header value. + +**Examples:** + +```python +span = posthog.get_active_span() +if span is not None: + headers["traceparent"] = span.traceparent() +``` + +### `posthog.shutdown` + +```python +shutdown() -> None +``` + +Attempt final delivery and permanently shut down the global client. + +Call this for final process cleanup, not at the end of each request or +serverless invocation that shares the module-level client. Use `flush()` +before returning from those invocations instead. Shutdown closes capture +lanes, stops background workers and the feature flag poller, and tears down +integrations. The global client is not recreated automatically after shutdown. + +This normally blocks until queued events have been attempted and cleanup +finishes. Failed or undrainable events may be dropped and reported through +logging or `on_error`; returning does not guarantee server receipt. Event +draining has no timeout. Queued spans get a final flush with up to 30 seconds +waiting for an in-flight flush, then a fresh 30-second drain budget; the last +HTTP request is not cut short. Unsent queued spans and still-open spans are +discarded with a warning. +Lifecycle cleanup is attempted once, and cleanup failures are logged without +retry. Calls made directly from SDK callbacks such as `on_error` are deferred +to avoid deadlocking the worker. If blocking completion is required, signal an application-owned +thread, return from the callback, and call `shutdown()` from that thread. +Do not wait inside a callback for another thread or task calling a lifecycle +method. + +**Examples:** + +```python +from posthog import shutdown +shutdown() +``` + +### `posthog.setup` + +```python +setup() -> Client +``` + +Create or return the global PostHog client configured by module settings. + +Most applications should either instantiate `Posthog` directly or set +`posthog.api_key`/other module settings before calling top-level helpers. +`setup()` is called automatically by global APIs such as `capture()`. + +**Returns:** + +Type | Description +---- | ----------- +Client | The global `Client` instance. If both `api_key` and +Client | `project_api_key` are missing or blank, the client is disabled and +Client | module-level calls become no-ops. + +## Types + +### `OptionalCaptureArgs` + +Bases: TypedDict + +Optional arguments for the capture method. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | | Unique identifier for the person associated with this event. If not set, the context distinct_id is used, if available, otherwise a UUID is generated, and the event is marked as personless. Setting context-level distinct_id's is recommended. | +`properties` | | Dictionary of properties to track with the event | +`timestamp` | | When the event occurred (defaults to current time). UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | +`uuid` | | Unique identifier for this specific event. If not provided, one is generated. The event UUID is returned, so you can correlate it with actions in your app (like showing users an error ID if you capture an exception). If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | +`groups` | | Group identifiers to associate with this event (format: {group_type: group_key}) | +`flags` | | A `FeatureFlagEvaluations` snapshot from `evaluate_flags()`. The exact flag values from the snapshot are attached to the event with no additional network call — prefer this over `send_feature_flags`. | +`send_feature_flags` | | Deprecated — prefer `flags` with a `FeatureFlagEvaluations` snapshot. Whether to include currently active feature flags in the event properties. Can be a boolean or a SendFeatureFlagsOptions object. Defaults to False. Fires a hidden `/flags` request on capture and may return different values than the ones the code branched on. | +`disable_geoip` | | Whether to disable GeoIP lookup for this event. Defaults to False. | +`options` | | Capture options for this event, such as `{"process_person_profile": False}`. Sent as given, for PostHog to validate. An option wins over its legacy `$` property, such as `$process_person_profile`. A value that is not a dict is logged and ignored. | + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`disable_geoip` | NotRequired[Optional[bool]] | +`distinct_id` | NotRequired[Optional[ID_TYPES]] | +`flags` | NotRequired[Optional[FeatureFlagEvaluations]] | +`groups` | NotRequired[Optional[Dict[str, str]]] | +`options` | NotRequired[Optional[Dict[str, Any]]] | +`properties` | NotRequired[Optional[Dict[str, Any]]] | +`send_feature_flags` | NotRequired[Optional[Union[bool, SendFeatureFlagsOptions]]] | +`timestamp` | NotRequired[Optional[Union[datetime, str]]] | +`uuid` | NotRequired[Optional[Union[str, UUID]]] | + +#### `posthog.args.OptionalCaptureArgs.disable_geoip` + +```python +disable_geoip: NotRequired[Optional[bool]] +``` + +#### `posthog.args.OptionalCaptureArgs.distinct_id` + +```python +distinct_id: NotRequired[Optional[ID_TYPES]] +``` + +#### `posthog.args.OptionalCaptureArgs.flags` + +```python +flags: NotRequired[Optional[FeatureFlagEvaluations]] +``` + +#### `posthog.args.OptionalCaptureArgs.groups` + +```python +groups: NotRequired[Optional[Dict[str, str]]] +``` + +#### `posthog.args.OptionalCaptureArgs.options` + +```python +options: NotRequired[Optional[Dict[str, Any]]] +``` + +#### `posthog.args.OptionalCaptureArgs.properties` + +```python +properties: NotRequired[Optional[Dict[str, Any]]] +``` + +#### `posthog.args.OptionalCaptureArgs.send_feature_flags` + +```python +send_feature_flags: NotRequired[Optional[Union[bool, SendFeatureFlagsOptions]]] +``` + +#### `posthog.args.OptionalCaptureArgs.timestamp` + +```python +timestamp: NotRequired[Optional[Union[datetime, str]]] +``` + +#### `posthog.args.OptionalCaptureArgs.uuid` + +```python +uuid: NotRequired[Optional[Union[str, UUID]]] +``` + +### `FeatureFlagEvaluations` + +```python +FeatureFlagEvaluations(host: _FeatureFlagEvaluationsHost, distinct_id: str, flags: Dict[str, _EvaluatedFlagRecord], groups: Optional[Mapping[str, Union[str, int]]] = None, disable_geoip: Optional[bool] = None, request_id: Optional[str] = None, evaluated_at: Optional[int] = None, errors_while_computing: bool = False, quota_limited: bool = False, minimal_flag_called_events: bool = False, accessed: Optional[Set[str]] = None, unresolved_flags: Optional[Mapping[str, UnresolvedFlagReason]] = None) -> None +``` + +A point-in-time snapshot of feature flag evaluations for a single distinct_id. + +Returned by `Client.evaluate_flags` — branch on `is_enabled` / +`get_flag` and pass the same object to `Client.capture` via the +`flags` option so the captured event carries the exact flag values the code +branched on. + +Example:: + + flags = posthog.evaluate_flags(distinct_id, person_properties={"plan": "enterprise"}) + if flags.is_enabled("new-dashboard"): + render_new_dashboard() + posthog.capture("page_viewed", distinct_id=distinct_id, flags=flags) + +To narrow the set of flags that get attached to a captured event, use the in-memory +helpers `only` and `only_accessed`. To narrow the set of flags requested +from the server in the first place, pass `flag_keys` to `Client.evaluate_flags`. + +**Functions:** + +Name | Description +---- | ----------- +`get_flag` | Return the flag value. Fires `$feature_flag_called` on first access. +`get_flag_payload` | Return the payload associated with a flag. +`is_enabled` | Return whether the flag is enabled. Fires `$feature_flag_called` on the +`only` | Return a filtered copy containing only flags with the given keys. Keys that +`only_accessed` | Return a filtered copy containing only flags accessed via `is_enabled` + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`keys` | List[str] | Return the flag keys that are part of this evaluation. +`unresolved_flags` | Mapping[str, UnresolvedFlagReason] | Return a read-only mapping of flag key to the reason local evaluation + +Internal — instances are created by the SDK via `Client.evaluate_flags()`. + +#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.get_flag` + +```python +get_flag(key: str) -> Optional[FlagValue] +``` + +Return the flag value. Fires `$feature_flag_called` on first access. + +Returns the variant string for multivariate flags, `True` for enabled flags +without a variant, `False` for disabled flags, and `None` for flags that +were not returned by the evaluation. + +#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.get_flag_payload` + +```python +get_flag_payload(key: str) -> Optional[Any] +``` + +Return the payload associated with a flag. + +Does not count as an access for `only_accessed` and does not fire any event. + +#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.is_enabled` + +```python +is_enabled(key: str, default_value: bool = False) -> bool +``` + +Return whether the flag is enabled. Fires `$feature_flag_called` on the +first access per (distinct_id, flag, value) tuple, deduped via the SDK's cache. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The feature flag key. | *required* +`default_value` | bool | Returned when the flag has no value in this evaluation — the key was not part of the evaluated set, or the evaluation itself came back empty (failed `/flags` request, quota limit, or no resolvable `distinct_id`). Defaults to `False`. | False + +A flag that has a value always wins over `default_value`, including a +disabled flag, which returns `False` even when `default_value=True`. + +#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.keys` + +```python +keys: List[str] +``` + +Return the flag keys that are part of this evaluation. + +#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.only` + +```python +only(keys: List[str]) -> FeatureFlagEvaluations +``` + +Return a filtered copy containing only flags with the given keys. Keys that +are not present in the evaluation are dropped and logged as a warning. + +#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.only_accessed` + +```python +only_accessed() -> FeatureFlagEvaluations +``` + +Return a filtered copy containing only flags accessed via `is_enabled` +or `get_flag` before this call. + +Order-dependent: if nothing has been accessed yet, the returned snapshot is +empty. The method honors its name — pre-access if you want a populated result. + +#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.unresolved_flags` + +```python +unresolved_flags: Mapping[str, UnresolvedFlagReason] +``` + +Return a read-only mapping of flag key to the reason local evaluation +left it without a value. + +A flag is listed when it has a loaded local definition, was in the requested +scope, was inconclusive locally, and no remote value filled the gap. Listed +flags are not part of `keys`, and reading one reports the +`local_evaluation_inconclusive` error instead of `flag_missing`. Reading +this mapping does not count as an access and does not fire any event. +Snapshots returned by `only` and `only_accessed`, and by +`AsyncClient.evaluate_flags()`, which evaluates only remotely, carry no +entries. + +### `FeatureFlagResult` + +```python +FeatureFlagResult(key: str, enabled: bool, variant: Optional[str], payload: Optional[Any], reason: Optional[str]) -> None +``` + +The result of calling a feature flag which includes the flag result, variant, and payload. + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`key` | str | The unique identifier of the feature flag. +`enabled` | bool | Whether the feature flag is enabled for the current context. +`variant` | Optional[str] | The variant value if the flag is enabled and has variants, None otherwise. +`payload` | Optional[Any] | Additional data associated with the feature flag, if any. +`reason` | Optional[str] | A description of why the flag was enabled or disabled, if available. + +**Functions:** + +Name | Description +---- | ----------- +`from_flag_details` | Create a FeatureFlagResult from a FeatureFlag object. +`from_value_and_payload` | Creates a FeatureFlagResult from a flag value and payload. +`get_value` | Returns the value of the flag. This is the variant if it exists, otherwise the enabled value. + +#### `posthog.types.FeatureFlagResult.enabled` + +```python +enabled: bool +``` + +#### `posthog.types.FeatureFlagResult.from_flag_details` + +```python +from_flag_details(details: Union[FeatureFlag, None], override_match_value: Optional[FlagValue] = None) -> FeatureFlagResult | None +``` + +Create a FeatureFlagResult from a FeatureFlag object. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`details` | Union[FeatureFlag, None] | The FeatureFlag object to convert. | *required* +`override_match_value` | Optional[FlagValue] | If provided, this value will be used to populate the enabled and variant fields instead of the values from the FeatureFlag. | None + +**Returns:** + +Type | Description +---- | ----------- +FeatureFlagResult \| None | FeatureFlagResult | None: A new FeatureFlagResult instance, or None if details is None. + +#### `posthog.types.FeatureFlagResult.from_value_and_payload` + +```python +from_value_and_payload(key: str, value: Union[FlagValue, None], payload: Any) -> Union[FeatureFlagResult, None] +``` + +Creates a FeatureFlagResult from a flag value and payload. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The unique identifier of the feature flag. | *required* +`value` | Union[FlagValue, None] | The value of the flag (string variant or boolean). | *required* +`payload` | Any | Additional data associated with the feature flag. | *required* + +**Returns:** + +Type | Description +---- | ----------- +Union[FeatureFlagResult, None] | Union[FeatureFlagResult, None]: A new FeatureFlagResult instance, or None if value is None. + +#### `posthog.types.FeatureFlagResult.get_value` + +```python +get_value() -> FlagValue +``` + +Returns the value of the flag. This is the variant if it exists, otherwise the enabled value. +This is the value we report as `$feature_flag_response` in the `$feature_flag_called` event. + +**Returns:** + +Name | Type | Description +---- | ---- | ----------- +`FlagValue` | FlagValue | Either a string variant or boolean value representing the flag's state. + +#### `posthog.types.FeatureFlagResult.key` + +```python +key: str +``` + +#### `posthog.types.FeatureFlagResult.payload` + +```python +payload: Optional[Any] +``` + +#### `posthog.types.FeatureFlagResult.reason` + +```python +reason: Optional[str] +``` + +#### `posthog.types.FeatureFlagResult.variant` + +```python +variant: Optional[str] +``` + +### `FeatureFlag` + +```python +FeatureFlag(key: str, enabled: bool, variant: Optional[str], reason: Optional[FlagReason], metadata: Union[FlagMetadata, LegacyFlagMetadata]) -> None +``` + +Detailed feature flag evaluation returned by the flags API. + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`key` | str | Feature flag key. +`enabled` | bool | Whether the flag is enabled for the evaluated user or group. +`variant` | Optional[str] | Variant key for multivariate flags, otherwise `None`. +`reason` | Optional[FlagReason] | Optional reason metadata explaining the result. +`metadata` | Union[FlagMetadata, LegacyFlagMetadata] | Payload and other metadata returned by the API. + +**Functions:** + +Name | Description +---- | ----------- +`from_json` | Build a flag from one entry of a flags API response. Missing `metadata` becomes empty legacy metadata. +`from_value_and_payload` | Build a flag from its value and payload. A string value is a variant, so the flag is enabled. +`get_value` | Return the variant for a multivariate flag, otherwise whether the flag is enabled. + +#### `posthog.types.FeatureFlag.enabled` + +```python +enabled: bool +``` + +#### `posthog.types.FeatureFlag.from_json` + +```python +from_json(resp: Any) -> FeatureFlag +``` + +Build a flag from one entry of a flags API response. Missing `metadata` becomes empty legacy metadata. + +#### `posthog.types.FeatureFlag.from_value_and_payload` + +```python +from_value_and_payload(key: str, value: FlagValue, payload: Any) -> FeatureFlag +``` + +Build a flag from its value and payload. A string value is a variant, so the flag is enabled. + +#### `posthog.types.FeatureFlag.get_value` + +```python +get_value() -> FlagValue +``` + +Return the variant for a multivariate flag, otherwise whether the flag is enabled. + +#### `posthog.types.FeatureFlag.key` + +```python +key: str +``` + +#### `posthog.types.FeatureFlag.metadata` + +```python +metadata: Union[FlagMetadata, LegacyFlagMetadata] +``` + +#### `posthog.types.FeatureFlag.reason` + +```python +reason: Optional[FlagReason] +``` + +#### `posthog.types.FeatureFlag.variant` + +```python +variant: Optional[str] +``` + +### `FlagsAndPayloads` + +Bases: TypedDict + +Feature flag values and payloads keyed by feature flag key. + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`featureFlags` | Optional[dict[str, FlagValue]] | Each flag's value, keyed by flag key. +`featureFlagPayloads` | Optional[dict[str, Any]] | Each flag's payload, keyed by flag key. + +#### `posthog.types.FlagsAndPayloads.featureFlagPayloads` + +```python +featureFlagPayloads: Optional[dict[str, Any]] +``` + +#### `posthog.types.FlagsAndPayloads.featureFlags` + +```python +featureFlags: Optional[dict[str, FlagValue]] +``` + +### `UnresolvedFlagReason` + +Bases: str, Enum + +Why local evaluation left a flag without a value. + +Reported by `FeatureFlagEvaluations.unresolved_flags`. Inheriting from +`str` keeps the members directly comparable to their string values. When more +than one cause applies, the first reason in the order below is reported. + +
+Values + +EXPERIENCE_CONTINUITY: The flag has experience continuity enabled, which + local evaluation never resolves. Change the flag to fix it. +UNSUPPORTED_DEFINITION: The flag uses something the local evaluator does not + support, such as a static cohort, an unknown operator, a malformed value, + a dependency with no loaded definition or a circular dependency. Change + the flag or upgrade the SDK to fix it. +UNRESOLVED_DEPENDENCY: A flag this flag depends on has a loaded definition + but was unresolved. +MISSING_CONTEXT: The call did not pass a property, group property or device + id the flag needs, or passed it in an unusable form. Pass it to fix it. + +
+ +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`EXPERIENCE_CONTINUITY` | | +`MISSING_CONTEXT` | | +`UNRESOLVED_DEPENDENCY` | | +`UNSUPPORTED_DEFINITION` | | + +#### `posthog.types.UnresolvedFlagReason.EXPERIENCE_CONTINUITY` + +```python +EXPERIENCE_CONTINUITY = 'experience_continuity' +``` + +#### `posthog.types.UnresolvedFlagReason.MISSING_CONTEXT` + +```python +MISSING_CONTEXT = 'missing_context' +``` + +#### `posthog.types.UnresolvedFlagReason.UNRESOLVED_DEPENDENCY` + +```python +UNRESOLVED_DEPENDENCY = 'unresolved_dependency' +``` + +#### `posthog.types.UnresolvedFlagReason.UNSUPPORTED_DEFINITION` + +```python +UNSUPPORTED_DEFINITION = 'unsupported_definition' +``` + +### `InconclusiveMatchError` + +```python +InconclusiveMatchError(*args, reason: Optional[UnresolvedFlagReason] = None) +``` + +Bases: Exception + +Raised when local evaluation can't determine a flag's value. `reason` says +why, as reported by `FeatureFlagEvaluations.unresolved_flags`. + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`reason` | UnresolvedFlagReason | Why local evaluation couldn't resolve the flag. + +#### `posthog.feature_flags.InconclusiveMatchError.reason` + +```python +reason: UnresolvedFlagReason = UnresolvedFlagReason.UNSUPPORTED_DEFINITION +``` + +### `RequiresServerEvaluation` + +```python +RequiresServerEvaluation(*args, reason: Optional[UnresolvedFlagReason] = None) +``` + +Bases: Exception + +Raised when feature flag evaluation requires server-side data that is not +available locally (e.g., static cohorts). + +This error should propagate immediately to trigger API fallback, unlike +InconclusiveMatchError which allows trying other conditions. + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`reason` | UnresolvedFlagReason | Why the flag needs server-side evaluation. + +#### `posthog.feature_flags.RequiresServerEvaluation.reason` + +```python +reason: UnresolvedFlagReason = UnresolvedFlagReason.UNSUPPORTED_DEFINITION +``` + +### `CaptureError` + +```python +CaptureError(status: int | str, message: str, *, endpoint: str, retry_after: Optional[float] = None, request_id: Optional[str] = None, attempts: Optional[int] = None, retry_exhausted: Optional[list[str]] = None, drops: Optional[list[tuple[str, Optional[str]]]] = None, event_results: Optional[dict[str, CaptureEventResult]] = None) +``` + +Bases: APIError + +A capture batch that was not fully delivered. + +Passed to `on_error` with the batch. `status` is the HTTP status of the +last attempt, or `0` when no response arrived (`__cause__` holds the +transport error). A 2xx `status` means the request succeeded but some +events were dropped or ran out of retries. + +`event_results` maps each event uuid to its last verdict from a 2xx +response. Events missing from it never got a verdict, because every +attempt that reached them failed as a whole request. + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`status` | | The HTTP status of the last attempt, or `0` when no response arrived. +`message` | | What went wrong. +`endpoint` | | The capture path the batch was sent to, so one `on_error` handler can tell the analytics and AI lanes apart. +`retry_after` | | Seconds the last response's `Retry-After` header asked to wait, or `None` when it sent none. +`request_id` | | The ID the SDK gave the batch. Every attempt sends the same ID. +`attempts` | | How many times the SDK sent the batch. +`retry_exhausted` | | Uuids the server asked the SDK to retry that were never delivered. +`drops` | | `(uuid, details)` pairs the server told the SDK to drop on a 2xx response. +`event_results` | | Each event uuid's last verdict from a 2xx response. + +**Functions:** + +Name | Description +---- | ----------- +`verdict_summary` | Count the undelivered events by verdict and reason, for example + +#### `posthog.capture_send.CaptureError.attempts` + +```python +attempts = attempts +``` + +#### `posthog.capture_send.CaptureError.drops` + +```python +drops = drops or [] +``` + +#### `posthog.capture_send.CaptureError.endpoint` + +```python +endpoint = endpoint +``` + +#### `posthog.capture_send.CaptureError.event_results` + +```python +event_results = event_results or {} +``` + +#### `posthog.capture_send.CaptureError.message` + +```python +message = message +``` + +#### `posthog.capture_send.CaptureError.request_id` + +```python +request_id = request_id +``` + +#### `posthog.capture_send.CaptureError.retry_after` + +```python +retry_after = retry_after +``` + +#### `posthog.capture_send.CaptureError.retry_exhausted` + +```python +retry_exhausted = retry_exhausted or [] +``` + +#### `posthog.capture_send.CaptureError.status` + +```python +status = status +``` + +#### `posthog.capture_send.CaptureError.verdict_summary` + +```python +verdict_summary() -> str +``` + +Count the undelivered events by verdict and reason, for example +`drop/llm_events_over_quota=2, retry/not_persisted=1`. + +Counts the events in `drops` and `retry_exhausted`: events the +server dropped, and events still pending retry after the last 2xx +response. A retry verdict followed by a failed request, for example a +503 on the last attempt, is not counted; `event_results` keeps it. +Empty when no event is counted. + +### `Span` + +A handle to a span. + +Every method is safe to call on any handle, including after `end()` and +on the inert handles returned when tracing is off, so calling code never +branches on whether tracing is running. Entering a handle (`with span:`) +makes it the active span for the block and ends it on exit, recording an +`Exception` raised inside it. A `BaseException` that is not an +`Exception` (`GeneratorExit`, `CancelledError`, `KeyboardInterrupt`) +still ends the span but is not recorded as a failure. + +**Functions:** + +Name | Description +---- | ----------- +`add_event` | Add a timestamped event to the span. Ignored after `end()`. +`end` | End the span and queue it for export. Idempotent. +`record_exception` | Record an exception as an `exception` event and mark the span `error`. +`set_attribute` | Set one attribute on the span; last write wins. Ignored after `end()`. +`set_attributes` | Set several attributes at once; last write wins per key. Ignored after `end()`. +`set_status` | Set the span status to `"ok"` or `"error"`. Ignored after `end()`. +`traceparent` | The W3C `traceparent` header value to propagate, or `None`. +`tracestate` | The W3C `tracestate` header value to propagate, or `None`. +`update_name` | Rename the span, for a name only known after it started. Ignored after `end()`. + +#### `posthog.tracing.span.Span.add_event` + +```python +add_event(name: str, attributes: Optional[Mapping[str, Any]] = None, timestamp: Union[datetime, float, None] = None) -> Span +``` + +Add a timestamped event to the span. Ignored after `end()`. + +`timestamp` defaults to now; it accepts a `datetime`, or seconds +since the epoch as a `float` or `int`. Returns the span, so calls +chain. + +**Examples:** + +```python +span.add_event("cache.miss", {"key": "user:42"}) +``` + +#### `posthog.tracing.span.Span.end` + +```python +end(end_time: Union[datetime, float, None] = None) -> None +``` + +End the span and queue it for export. Idempotent. + +`end_time` defaults to now; it accepts a `datetime`, or seconds +since the epoch as a `float` or `int`, and is never earlier than +the start. Later calls, and later mutations, no-op. `with span:` +ends the span on exit, so call this only for spans started manually. + +**Examples:** + +```python +span = posthog.start_span("job") +try: + run_job() +finally: + span.end() +``` + +#### `posthog.tracing.span.Span.record_exception` + +```python +record_exception(exception: BaseException) -> Span +``` + +Record an exception as an `exception` event and mark the span `error`. + +Ignored after `end()`. The event carries `exception.type`, +`exception.message` and, for a raised exception, +`exception.stacktrace` (its last `max_attribute_value_length` +characters, so a chained traceback keeps the outermost exception and +may lose its cause). Returns the span, so calls chain. Inside +`with span:` a raised `Exception` is recorded automatically, so +this is for exceptions that are caught and handled. + +**Examples:** + +```python +try: + charge(card) +except PaymentError as e: + span.record_exception(e) +``` + +#### `posthog.tracing.span.Span.set_attribute` + +```python +set_attribute(key: str, value: Any) -> Span +``` + +Set one attribute on the span; last write wins. Ignored after `end()`. + +Returns the span, so calls chain. Prefer primitive values: strings, +booleans, integers and floats keep their type. Lists and mappings are +sent as OTLP arrays and maps, but PostHog stores them as serialized +strings. `None` removes the key; anything else is stringified. + +**Examples:** + +```python +span.set_attribute("http.status_code", 200).set_attribute("cache.hit", True) +``` + +#### `posthog.tracing.span.Span.set_attributes` + +```python +set_attributes(attributes: Mapping[str, Any]) -> Span +``` + +Set several attributes at once; last write wins per key. Ignored after `end()`. + +Returns the span, so calls chain. Same encoding as `set_attribute()`. + +**Examples:** + +```python +span.set_attributes({"db.system": "postgresql", "db.rows": 12}) +``` + +#### `posthog.tracing.span.Span.set_status` + +```python +set_status(code: str, message: Optional[str] = None) -> Span +``` + +Set the span status to `"ok"` or `"error"`. Ignored after `end()`. + +Unset by default. Any other `code` is ignored. `ok` is final for the +scoped form: an `Exception` raised inside `with span:` does not override +it. Returns the span, so calls chain. + +**Examples:** + +```python +span.set_status("error", "upstream timed out") +``` + +#### `posthog.tracing.span.Span.traceparent` + +```python +traceparent() -> Optional[str] +``` + +The W3C `traceparent` header value to propagate, or `None`. + +#### `posthog.tracing.span.Span.tracestate` + +```python +tracestate() -> Optional[str] +``` + +The W3C `tracestate` header value to propagate, or `None`. + +#### `posthog.tracing.span.Span.update_name` + +```python +update_name(name: str) -> Span +``` + +Rename the span, for a name only known after it started. Ignored after `end()`. + +Returns the span, so calls chain. + +**Examples:** + +```python +span = posthog.start_span("http.request") +span.update_name(f"{request.method} {route.pattern}") +``` + +### `Emails` + +```python +Emails(secret_key: Optional[str], *, host: Optional[str] = None, timeout: float = 15, max_retries: int = 2, disabled: bool = False, session: Optional[requests.Session] = None) -> None +``` + +Bases: _EmailsBase + +Sends transactional email from PostHog email templates. + +`host` is your PostHog app host. PostHog Cloud ingestion hosts map to the +app host, so `us.i.posthog.com` sends to `us.posthog.com`. Any other +host is used as given. + +Each send retries network errors, rate limits and an unavailable service up +to `max_retries` times, with the same idempotency key. Set +`max_retries=0` when a job runner already retries the send. `timeout` +is in seconds, per request. Pass `session` to send through your own +`requests.Session`, for example a test double. + +**Functions:** + +Name | Description +---- | ----------- +`close` | Close the HTTP connections this client opened. A `session` you passed in stays open. +`get` | Read what happened to an email that `send` queued. +`send` | Queue one email from a transactional email template. + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`disabled` | | +`host` | | +`max_retries` | | +`secret_key` | | +`timeout` | | + +#### `posthog.emails.Emails.close` + +```python +close() -> None +``` + +Close the HTTP connections this client opened. A `session` you passed in stays open. + +#### `posthog.emails.Emails.disabled` + +```python +disabled = disabled +``` + +#### `posthog.emails.Emails.get` + +```python +get(message_id: Union[str, UUID]) -> EmailStatus +``` + +Read what happened to an email that `send` queued. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`message_id` | Union[str, UUID] | The `id` of the `SendEmailResult` that `send` returned. | *required* + +**Returns:** + +Type | Description +---- | ----------- +EmailStatus | The email's latest status, for 30 days after the send. + +**Raises:** + +Type | Description +---- | ----------- +EmailError | PostHog did not answer with a status. `code` is `not_found` for an unknown ID, an email older than 30 days, or for a few seconds after the send. +ValueError | `message_id` is not a message ID. + +**Examples:** + +```python +result = emails.send(to="user@example.com", template_id="...") +status = emails.get(result.id) +if status.status == "bounced": + print(status.reason) +``` + +#### `posthog.emails.Emails.host` + +```python +host = determine_app_host(host) +``` + +#### `posthog.emails.Emails.max_retries` + +```python +max_retries = max_retries +``` + +#### `posthog.emails.Emails.secret_key` + +```python +secret_key = secret_key.strip() if isinstance(secret_key, str) else None +``` + +#### `posthog.emails.Emails.send` + +```python +send(*, to: str, template_id: Union[str, UUID], template_data: Optional[Mapping[str, Any]] = None, distinct_id: Optional[str] = None, idempotency_key: Optional[str] = None, max_retries: Optional[int] = None) -> SendEmailResult +``` + +Queue one email from a transactional email template. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`to` | str | One recipient email address. | *required* +`template_id` | Union[str, UUID] | The UUID of a template made transactional in PostHog. | *required* +`template_data` | Optional[Mapping[str, Any]] | Values for the template's `{{ variables.* }}` placeholders. Values are encoded the way capture encodes properties. | None +`distinct_id` | Optional[str] | The recipient's distinct ID in your product. The email's events go to this person. Without it, they go to a person whose distinct ID is the email address. | None +`idempotency_key` | Optional[str] | 1 to 255 visible ASCII characters. A send with a key that PostHog has already accepted queues nothing and returns the first send's result. Without it, the client makes a key that covers its own retries. Pass a key derived from what the email is for, such as `welcome:user_123`, to cover retries outside this call, too. | None +`max_retries` | Optional[int] | Overrides the client's maximum number of retries for this send. `0` turns retries off. | None + +**Returns:** + +Type | Description +---- | ----------- +SendEmailResult | The queued email. A result means PostHog queued the email, not that +SendEmailResult | it was delivered. + +**Raises:** + +Type | Description +---- | ----------- +EmailError | PostHog did not queue the email, or the client could not tell whether it did. +(TypeError, ValueError) | An argument has the wrong type or format. + +**Examples:** + +```python +emails.send( + to="user@example.com", + template_id="0190f6d2-7c1e-7b3a-9f5e-1c2d3e4f5a6b", + template_data={"name": "Ada"}, + distinct_id="user_123", + idempotency_key="welcome:user_123", +) +``` + +#### `posthog.emails.Emails.timeout` + +```python +timeout = timeout +``` + +### `EmailError` + +```python +EmailError(*, name: str, code: str, message: str, status_code: Optional[int] = None, why: Optional[str] = None, fix: Optional[str] = None, docs_url: Optional[str] = None, field: Optional[str] = None, retry_after: Optional[float] = None) -> None +``` + +Bases: Exception + +PostHog did not queue the email, or the client could not tell whether it did. + +Branch on `code`, PostHog's stable error code, or on `name`, a coarse +category of the failure. The message never contains the secret key or the +request body. + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`name` | | A coarse category of the failure. +`code` | | PostHog's stable error code. +`message` | | What went wrong. It never contains the secret key or the request body. +`status_code` | | The HTTP status of the response, or `None` when none arrived. +`why` | | PostHog's explanation of the failure, when it gave one. +`fix` | | What to change to fix it, when PostHog said. +`docs_url` | | A docs page about the error, when PostHog gave one. +`field` | | The request field the error is about, when there is one. +`retry_after` | | Seconds to wait before retrying, when PostHog said. + +#### `posthog.emails.EmailError.code` + +```python +code = code +``` + +#### `posthog.emails.EmailError.docs_url` + +```python +docs_url = docs_url +``` + +#### `posthog.emails.EmailError.field` + +```python +field = field +``` + +#### `posthog.emails.EmailError.fix` + +```python +fix = fix +``` + +#### `posthog.emails.EmailError.message` + +```python +message = message +``` + +#### `posthog.emails.EmailError.name` + +```python +name = name +``` + +#### `posthog.emails.EmailError.retry_after` + +```python +retry_after = retry_after +``` + +#### `posthog.emails.EmailError.status_code` + +```python +status_code = status_code +``` + +#### `posthog.emails.EmailError.why` + +```python +why = why +``` + +### `SendEmailResult` + +```python +SendEmailResult(id: str, replayed: bool = False, warnings: tuple[EmailWarning, ...] = ()) -> None +``` + +The queued email. `id` is its message ID. + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`id` | str | The email's message ID. +`replayed` | bool | `True` when the response says PostHog replayed an earlier send for the same `idempotency_key` instead of sending again. +`warnings` | tuple[EmailWarning, ...] | Warnings PostHog returned about the request. + +#### `posthog.emails.SendEmailResult.id` + +```python +id: str +``` + +#### `posthog.emails.SendEmailResult.replayed` + +```python +replayed: bool = False +``` + +#### `posthog.emails.SendEmailResult.warnings` + +```python +warnings: tuple[EmailWarning, ...] = () +``` diff --git a/references/posthog-python-migration.md b/references/posthog-python-migration.md new file mode 100644 index 000000000..1c6c18564 --- /dev/null +++ b/references/posthog-python-migration.md @@ -0,0 +1,269 @@ +--- +title: PostHog Python SDK migration guides +sidebar: Docs +showTitle: true +--- + +# Migrating from posthog 7.x to 8.0 + +posthog 8.0 sends every event through capture v1 and removes the legacy capture path. +Most apps upgrade without code changes. +Read the checklist first, then the sections that apply to you. + +For the changes every capture v1 SDK shares, such as event options, limits and per-event results, see [Upgrading to capture v1 SDKs](https://posthog.com/docs/libraries/capture-v1). + +## Checklist + +You need to change code if your app does any of these: + +- passes `Client` or `AsyncPosthog` constructor arguments by position after `host` +- calls `posthog.request.post` or `posthog.request.flags` with positional arguments after `path` or `host` +- sets `$exception_level` or another reserved exception property in `capture_exception(properties=...)` +- sets `capture_mode`, `POSTHOG_CAPTURE_MODE` or `gzip` +- imports `CaptureMode`, `posthog.capture_mode`, `CaptureV1Error`, `posthog.capture_v1`, `request.batch_post`, `EVENTS_ENDPOINT` or `AI_EVENTS_ENDPOINT` +- sends events to a self-hosted PostHog that does not serve the capture v1 endpoints +- sends events through a proxy that redirects capture requests to another host +- reuses one event `uuid` for more than one event +- sets `$process_person_profile` to turn person processing on for events without a distinct ID +- passes strings such as `"true"` for `$cookieless_mode`, `$ignore_sent_at` or `$process_person_profile` +- expects `super_properties` to override properties passed to a single call, including `$set` and `$groups` +- sets `$is_server`, `$geoip_disable` or a system property such as `$os` and expects the SDK's value to win +- tests the AI integrations with a mock client and asserts on `capture` +- passes its own client object to the AI integrations +- sets `$lib` or `$lib_version`, or filters on `$lib = "posthog-python-mcp"` +- passes a personal API key (`phx_...`) as the project API key +- reads event content from the SDK's debug log +- calls `alias()` without a `distinct_id` and relies on the context distinct ID +- runs the Django, Flask or ASGI integration on 7.68 to 7.70 without tracing headers turned on (they are now read by default) + +## Endpoints + +| Method | Endpoint | +| --- | --- | +| `capture`, `set`, `set_once`, `alias`, `group_identify`, `capture_exception` | `/i/v1/analytics/events` | +| `capture_ai`, `capture_ai_immediate`, and the AI wrapper integrations | `/i/v1/ai/events` | +| OpenTelemetry (`posthog.ai.otel`) | `/i/v0/ai/otel`, unchanged | + +If you send events to a self-hosted PostHog, check that it serves both endpoints before you upgrade. +An endpoint that is not served drops every event sent to it. + +If you send events through your own reverse proxy, update it before you upgrade: + +- Forward `/i/v1/analytics/events` and `/i/v1/ai/events` to PostHog. +- Pass `Authorization` (the project token, sent as `Bearer `), every `PostHog-*` header, `Content-Type`, `Content-Encoding` and `User-Agent` through unchanged. PostHog answers `401` without `Authorization` and `400` without the others, and the batch fails. +- Accept request bodies up to 20 MB. AI requests are often larger than 1 MB, the default limit in nginx and ingress-nginx. A `413` from the proxy fails the batch. + +PostHog's managed reverse proxy already does this. See the [proxy guides](https://posthog.com/docs/advanced/proxy). + +The SDK follows a `307` or `308` redirect only to the origin of `host` (same scheme, host and port), at most 5 times. +Any other redirect fails the batch and reaches `on_error`. +In 7.x the sync client followed redirects to any origin. +If a proxy redirects capture requests to another host, point `host` at the final host. + +## SDK identity + +PostHog sets `$lib` and `$lib_version` on every event from the `PostHog-Sdk-Info` request header, which is always `posthog-python/`. +The SDK removes `$lib` and `$lib_version` from the properties it sends. +A value you set in a call, in `super_properties` or in `before_send` does not reach PostHog. + +MCP instrumentation no longer relabels the client. +In 7.x, `posthog.mcp.instrument()` and `PostHogMCP` set the client's identity to `posthog-python-mcp`. +With the default client, or any client the app also used, every event and feature flag request from the app then reported `posthog-python-mcp`, not only the MCP events. +In 8.0, MCP events report `posthog-python` like all other events. +To find MCP traffic, filter on the `$mcp_*` events and properties instead of `$lib`. + +## Removed options and APIs + +| Removed | Use instead | +| --- | --- | +| `capture_mode`, `CaptureMode`, `POSTHOG_CAPTURE_MODE` | Nothing. Capture v1 is the only path. Setting `posthog.capture_mode` on the module has no effect. | +| `gzip=True` | `capture_compression=CaptureCompression.GZIP`. The default is no compression. `POSTHOG_CAPTURE_COMPRESSION` still works. | +| `CaptureV1Error` (`from posthog.capture_v1 import CaptureV1Error`) | `CaptureError` (`from posthog import CaptureError`). There is no alias. | +| `posthog.capture_v1` | `posthog.capture_event` and `posthog.capture_send` | +| `request.batch_post`, `async_batch_post`, `EVENTS_ENDPOINT`, `AI_EVENTS_ENDPOINT` | Nothing. Send events through a client. | +| The `gzip` parameter of `request.post` and `request.flags` | Nothing. The parameters after `path` in `post` and after `host` in `flags` are keyword-only, so a 7.x call that passes them by position raises `TypeError`. A `gzip=` keyword raises no error: it is sent as a field in the request body. | +| The `backoff` dependency | Add it to your own requirements if your code imports it. | + +The `posthoganalytics` package has the same changes. For example, import `CaptureError` from `posthoganalytics`. + +Constructor arguments after `host` are keyword-only: + +```python +# 7.x +Posthog("", "https://us.i.posthog.com", True) + +# 8.0 +Posthog("", host="https://us.i.posthog.com", debug=True) +``` + +## Errors and `on_error` + +Capture v1 returns a result for every event, even when the request succeeds. +An event can be dropped inside a 2xx response, for example when it is over a product quota. +8.0 reports those events as failures. +When the whole request is over the billing limit, capture returns a `402` with no per-event results instead. + +- Every capture failure is a `CaptureError`, a subclass of `APIError`. It has these fields: + - `status`: the HTTP status, or `0` when the request never got a response. With `0`, `__cause__` holds the network error. In 7.x, `on_error` received the raw `requests` exception. + - `endpoint`, `request_id` and `attempts` + - `drops`: `(uuid, details)` pairs for the events capture dropped in a 2xx response + - `retry_exhausted`: the uuids still waiting for a retry after the last attempt + - `event_results`: a `CaptureEventResult(result, details)` for each uuid that got a result in a 2xx response, from `posthog.capture_send`. An event missing from it never got a result. + - `verdict_summary()`: counts such as `drop/llm_events_over_quota=1, retry/not_persisted=1` +- `on_error(error, batch)` receives every failure, including in `sync_mode` and from `AsyncPosthog.capture_immediate`. +- With `on_error` set, the SDK logs nothing for a failed batch. +- Without `on_error`, the SDK logs one line per failed batch, for example `2 event(s) not persisted by /i/v1/analytics/events: ...`. The line never contains event content or the server's response text. After a 2xx response it also counts the lost events by PostHog's reason, for example `drop/event_too_big=1`. +- The 7.x lines `error uploading: ...`, `async capture upload failed (...)` and `Immediate async capture failed (...)` are gone. In `sync_mode`, a failed request no longer logs the 7.x `Error in capture: ...` line, or the same line for another method. Update any log-based alerts that match these lines. +- In `sync_mode`, a failed `capture()` calls `on_error` and returns `None`. With `debug=True` it calls `on_error` and then re-raises the error. +- In `sync_mode`, a failed request is retried like a background batch, in the calling thread. With the default `max_retries` of 3, a call makes up to 4 attempts, and each attempt can take up to `timeout` (`capture_ai_timeout` for AI events). With the defaults, an analytics event whose requests keep timing out blocks the call for about a minute. 7.x made one attempt. +- If a capture inside `on_error` fails, the SDK logs the failure and does not call `on_error` again. +- The SDK retries `404`, `408`, `429`, `500`, `502`, `503` and `504` responses and network errors. Other error statuses fail the batch at once, including the other 5xx codes. 7.x retried `429` and every 5xx, but not `404`. Capture itself does not return `429`; a proxy in front of it can. A `404` that lasts through every attempt can mean the PostHog deployment has no capture v1 route, for example an older self-hosted install. +- Each attempt makes one connection try. 7.x also retried a failed connection up to twice inside each attempt, so a capture request could block for up to three times the `timeout` per attempt. +- Retries start at 100 ms and double each attempt, up to 30 seconds. Each wait varies randomly by up to 25%, so many clients do not retry at the same moment. A `Retry-After` header sets a minimum wait, up to 30 seconds. The `Consumer` default is 3 retries, down from 10. In 7.x the first retry waited up to 1 second. +- The SDK's own log lines no longer contain event content: the debug line for a queued event names the event and the lane, and the invalid timestamp warning no longer includes the value. When your `before_send` hook raises, the error line names the event and the exception type, not the exception message, which can hold the data the hook was removing. The traceback is logged at debug level. To inspect events, use `before_send`. +- A personal API key (`phx_...`) passed as the project API key disables the client and logs one error, as an empty key does. Capture rejects personal API keys, and the SDK no longer sends one with every request. + +## Invalid events + +Capture rejects a whole batch when one event cannot be parsed. +8.0 drops only the invalid event, so the rest of the batch is delivered. + +- An event whose name is missing, `None`, empty or not a string is dropped. The SDK logs one warning per reason per process. 7.x sent it. +- An event whose `before_send` hook sets `distinct_id` to `None` or a non-string is dropped, with one warning per reason per process. The SDK does not convert the hook's value or make up an ID. 7.x sent it, and capture would reject the whole batch. +- PostHog drops an event whose name is over 200 UTF-8 bytes (`event_name_too_long`), and `on_error` reports it. 7.x sent events to legacy capture, which had no event name limit. +- A `NaN` or infinite `float` value is sent as `null`, as in 7.75 and later, and the event is kept. Earlier 7.x versions sent invalid JSON, which made capture reject the whole batch. A `NaN` value in `$set` sets that person property to `null`. +- An event that cannot be serialized to JSON, for example one with a circular reference, is dropped and logged. As in 7.x, a circular reference logs `Error in capture: maximum recursion depth exceeded` with a long traceback. +- A property or option key that JSON cannot represent, such as a tuple, is dropped without a log, and the rest of the event is sent. 7.x dropped the event. `int`, finite `float`, `bool` and `None` keys are still converted to strings. A `NaN` or infinite `float` key still drops the event. + +## Event uuids + +- Each event needs its own uuid. Capture keeps the first event for each uuid in a request and drops the later copies. `on_error` reports each dropped copy in `CaptureError.drops` as `(uuid, "duplicate_event_uuid")`. 7.x sent every copy. Older PostHog versions reject the whole batch with a 400 `duplicate_event_uuid` instead. +- The SDK accepts a uuid with hyphens, 32 hex digits, `{...}` braces or a `urn:uuid:` prefix, in any case. It sends the lowercase hyphenated form and returns that form from `capture`. +- Any other value is replaced with a generated uuid, and the SDK logs one warning that names the rule, not the value. An empty string counts as unset, so the SDK generates a uuid without a warning. This applies to `AsyncPosthog` too. +- Generated uuids are UUIDv7. + +## Session and window IDs + +Capture v1 sends `$session_id` and `$window_id` as top-level event fields, not as properties. +The SDK moves them out of `properties` for you. + +- A string is sent as given, including `""`. +- `None` counts as unset and is removed. +- Any other value is removed and not sent, because capture would reject the whole batch. 7.x sent it as a property. The SDK logs a warning for each one, with the key and the value's type but not the value. + +## Identity and request context + +- An empty or whitespace-only `distinct_id` counts as missing, so the event gets a generated personless distinct ID. 7.x sent a whitespace-only ID, and capture dropped the event. +- Generated personless distinct IDs are UUIDv7. 7.x generated UUIDv4. +- `group_identify` uses the `distinct_id` you pass, then the context distinct ID, then `$_`. 7.x fell back to a random ID, which created a new person on every call. If `$_` is over 200 bytes, pass `distinct_id`: PostHog drops an event whose distinct ID is over 200 bytes. +- PostHog drops an event whose `distinct_id` is over 200 UTF-8 bytes (`distinct_id_too_large`), and `on_error` reports it. 7.x sent events to legacy capture, which shortened the ID to 200 characters and kept the event. +- `group_identify` no longer adds the context session ID. A session takes the person of its latest event, and this event's distinct ID is not the session's user. +- `alias()` needs an explicit `distinct_id`. It no longer falls back to the context distinct ID, which can come from a request header the client controls. Without one, the call is dropped with a warning. +- Context tags and context options now apply to `alias` and `group_identify`, below the call's own values, as they do for `capture`. +- Person processing turned off in the context, with `set_context_option("process_person_profile", False)` or a `$process_person_profile` tag, or in `super_options`, now applies to `$create_alias` and `$groupidentify` events too. A `$process_person_profile: False` in `super_properties` already did this in 7.x. PostHog drops these events when person processing is off, with an ingestion warning. Capture accepts them, so `on_error` does not report the drop. To keep them, turn person processing off per call instead. + +## Tracing headers + +The Django, Flask and ASGI integrations read the `X-PostHog-Distinct-Id` and `X-PostHog-Session-Id` headers by default, as in 7.71 and later. +The headers only fill a missing distinct ID or session ID, and any client can set them, so never use them for authentication. + +- To turn this off, set `POSTHOG_MW_CAPTURE_TRACING_HEADERS = False` in Django, or pass `capture_tracing_headers=False` to the Flask or ASGI integration. +- The 7.68 `trust_tracing_headers` and `POSTHOG_MW_TRUST_TRACING_HEADERS` names still work, but are deprecated. +- If you upgrade from 7.68, 7.69 or 7.70 and never turned these headers on, they are now read. + +## Exceptions + +- `capture_exception` takes a `level=` argument on `Client`, `AsyncPosthog` and the module. It sets `$exception_level`, for example `"warning"` or `"fatal"`. The default is `"error"`, and an unknown value counts as unset. +- `capture_exception` ignores reserved exception properties in `properties`, such as `$exception_list`, `$exception_level` and `$exception_source`. In 7.x they overrode the SDK's values, with a `DeprecationWarning`. Use `level=` instead of `$exception_level`. +- `AsyncPosthog.capture_exception` now sends `$exception_level`. +- `AsyncPosthog.capture_exception` now adds `$trace_id` and `$span_id` from the active OpenTelemetry span, like `Client.capture_exception`. In 7.x it added them only with `capture_trace_context=True`. + +## Event options + +Capture v1 sends processing options in an `options` object, next to `properties`. + +- `capture`, `capture_ai`, `set`, `set_once`, `alias`, `group_identify` and `capture_exception` take an `options` argument: + + ```python + posthog.capture("signed_up", distinct_id="user-1", options={"cookieless_mode": True}) + ``` + +- `super_options`, a new `Client`, `AsyncPosthog` and module setting, sets options on every event, like `super_properties`. +- `set_context_option(key, value)` sets an option for the current context, like `tag()`. `get_context_options()` returns the options of the current context. Both exist on `Client` and as module functions. `AsyncPosthog` has no context methods, like it has no `tag()`, so call the module functions. They apply to every client in the context. +- Context tags and options reach `capture`, `capture_ai`, `capture_exception`, `alias` and `group_identify`. Context options also reach `set` and `set_once`. `super_properties` and `super_options` reach every method. +- Options are sent as given. PostHog validates them. It drops an event with an option value it cannot read, such as a list for `cookieless_mode`, as `invalid_options`, and `on_error` reports it. +- An `options` value that is not a dict is logged as an error and ignored. The event is still sent. +- The legacy option properties, the `$`-prefixed properties legacy capture reads as settings, still work. They fill the matching option only when it is unset, and the SDK always removes them from `properties`. Their values are no longer converted, so pass `True` or `False`, not `"true"`. + + | Legacy option property | Option | + | --- | --- | + | `$cookieless_mode` | `cookieless_mode` | + | `$ignore_sent_at` | `disable_skew_correction` | + | `$product_tour_id` | `product_tour_id` | + | `$process_person_profile` | `process_person_profile` | + +- An option set at any layer wins over its legacy property set at any layer. For example, `super_options={"cookieless_mode": True}` wins over an event's `$cookieless_mode: False`. When you move a default to options, move the per-event overrides of that key to options too. +- A `None` option counts as unset, so a later step can fill it. + +Values apply in this order. Steps 2 to 4 fill only the options and properties that the steps before them left unset: + +1. the `options` and `properties` of the call, then the call's `disable_geoip` argument, which fills `$geoip_disable` +2. context options and tags +3. `super_options` and `super_properties` +4. values the SDK sets: `$is_server` from `is_server`, `$geoip_disable` from the client's `disable_geoip` setting, system properties such as `$os` and `$python_version`, the `$feature/` and `$active_feature_flags` properties from `flags` or `send_feature_flags`, `$trace_id` and `$span_id` from the active OpenTelemetry span (in `capture` and `capture_ai` only with `capture_trace_context=True`, and always in `capture_exception`), `options.process_person_profile = false` for events without a distinct ID, and `$release_id` from `POSTHOG_RELEASE_ID` +5. `before_send`, which sees the result of steps 1 to 4 and can change or remove any of it +6. legacy properties, which fill unset options and are then removed + +A property set to `None` counts as set, so no later step fills it. +`$set`, `$set_once`, `$groups` and `$group_set` fill one level deep. +When the call and a later step both set one of them to a dict, the later step adds only the keys the call left out. +For example, `super_properties={"$set": {"plan": "free", "source": "web"}}` and a call with `properties={"$set": {"plan": "pro"}}` send `{"plan": "pro", "source": "web"}`. + +These changes follow from this order: + +- Properties passed to a call now override `super_properties`. `super_properties` can no longer change `$lib` or `$lib_version`. +- A `$is_server`, `$geoip_disable` or system property such as `$os` that you set in a call or a context tag now wins over the SDK's value. In 7.x the SDK overwrote it. `super_properties` win over the SDK's value too, so `super_properties={"$geoip_disable": False}` turns GeoIP lookup on for events, even with `disable_geoip=True`. +- Feature flag properties from `flags` or `send_feature_flags` still lose to the same key in the call's `properties`, a context tag or `super_properties`, as in 7.x. +- A `$trace_id` or `$span_id` in a context tag or `super_properties` now replaces the active OpenTelemetry span's IDs. In 7.x the span's IDs beat context tags. Setting either key in the call's `properties`, a context tag or `super_properties` stops the SDK adding both. In 7.x the SDK still added the other key. +- A `disable_geoip` argument to a call belongs to that event, so it wins over context tags and `super_properties`. A `$geoip_disable` in the call's own `properties` still wins over it. `disable_geoip=False` now sends `$geoip_disable: false`. In 7.x it sent no `$geoip_disable` property. +- The `groups` argument merges into a `$groups` property of the call, and wins key by key. In 7.x it replaced the property. MCP events merge their identity's groups into a custom `$groups` property the same way. +- A `$set`, `$set_once`, `$groups` or `$group_set` in `super_properties` no longer replaces the whole value of the call. The two merge, and the call wins key by key. +- An event without a distinct ID gets `options.process_person_profile = false`. A `$process_person_profile: true` property no longer turns person processing back on. Set the option instead. +- Review your `before_send` hooks for the new event shape. To change an option, edit `options`: a legacy property that `before_send` adds does not replace an option that is already set. A hook that returns a new dict must copy `options`, or the event is sent without them. That includes the `process_person_profile = false` the SDK sets for an event without a distinct ID, so PostHog would create a person for its generated ID. +- When the server enables minimal `$feature_flag_called` events, those events now keep `$window_id` and the legacy option properties, so their options still apply. + +## AI capture + +- `capture_ai` and the AI wrapper integrations send to `/i/v1/ai/events`. The AI endpoint accepts events up to 8 MiB. +- Send `$ai_` events with `capture_ai`. `capture()` sends every event to `/i/v1/analytics/events`, including `$ai_` events, which PostHog accepts there for now. +- `capture_ai` sends any event name to the AI endpoint. PostHog drops an event whose name does not start with `$ai_` as `misrouted_event`, and `on_error` reports it. The SDK writes only a debug log. +- The OpenTelemetry integration (`PostHogSpanProcessor`, `PostHogTraceExporter`) is unchanged: it still exports spans over OTLP to `/i/v0/ai/otel`. +- `enable_full_ai_capture` now controls only content: string truncation and media redaction. It no longer chooses the endpoint. `_use_ai_lane` and `_enable_multimodal_capture` still work as aliases. +- The AI integrations call your client's `capture_ai`, and never `capture`. A custom client object must implement `capture_ai` and accept its `options` keyword argument. If the client has no `capture_ai`, the wrapper, callback handler or tracing processor logs one error when you create it and captures no AI events; provider calls still work. Before, such a client got `capture` calls. +- Tests that pass a `Mock` client to an AI integration must assert on `mock.capture_ai`, not `mock.capture`. +- When an AI integration or MCP falls back to a trace, run or session ID, it turns person processing off with a per-event option. This is the only option the integrations pass. It wins over `super_options`, context options and a `$process_person_profile: true` property. `before_send` can still change it. +- An empty or whitespace-only distinct ID passed to an AI integration, such as `posthog_distinct_id=""`, counts as missing, so the integration falls back the same way and turns person processing off. In 7.x, an empty ID gave non-streaming wrapper calls a random personless ID. Streaming calls, embeddings and LangChain used the trace or run ID with person processing on. OpenAI Agents already treated an empty ID as missing. Every integration sent a whitespace-only ID as given, with person processing on. +- The AI integrations have no `options` argument. To set options on their events, use `set_context_option`, `super_options`, or a legacy property such as `$cookieless_mode` in `posthog_properties`. +- MCP's `PostHogCaptureEvent` has an `options` key. +- New `Client` and `AsyncPosthog` arguments for the AI lane: + - `capture_ai_compression`: default none. `POSTHOG_CAPTURE_COMPRESSION` does not apply to it. + - `capture_ai_max_queue_size`: default 1000 + - `capture_ai_timeout`: default 30 seconds + - `capture_ai_max_event_bytes`: default 8 MiB plus 64 KiB. You can only lower it. +- The AI lane does not use `max_queue_size`, `timeout` or `capture_compression`. An invalid `capture_ai_*` value raises `ValueError` when you create the client. +- The module-level client has no AI lane settings. To change them, create a `Client`. +- `AsyncPosthog` has `capture_ai` and `capture_ai_immediate`. +- `AsyncPosthog` sends events with `requests` in a worker thread. It uses `httpx` only for feature flags and remote config. +- `AsyncPosthog` accepts `privacy_mode` and `enable_full_ai_capture`, with the same meaning as on `Client`. An AI integration given an `AsyncPosthog` client used to always truncate and redact media. + +## Batching and size limits + +- An analytics event over 10 MiB is dropped and logged, because no capture request can carry it. 7.x dropped events over 900 KiB. Capture drops an event over its own per-event limit and reports it in `CaptureError.drops` as `event_too_big`. +- An AI event over `capture_ai_max_event_bytes` is dropped and logged. +- A batch stops before an event that would take it past 5 MiB. A larger event is sent alone. +- `flush()` and `shutdown()` drain the analytics and AI queues at the same time. + +## OpenFeature provider + +`openfeature-provider-posthog` 0.2.0 works with posthog 7.x and 8.x. +Older provider versions require posthog below 8.0, so upgrade the provider before or with posthog. diff --git a/references/posthog-python-reference.md b/references/posthog-python-reference.md new file mode 100644 index 000000000..940600cf7 --- /dev/null +++ b/references/posthog-python-reference.md @@ -0,0 +1,4102 @@ +--- +title: "PostHog Python SDK" +sidebar: Docs +showTitle: true +contentMaxWidthClass: max-w-full +platformLogo: python +tableOfContents: + - value: "Getting started" + url: getting-started + depth: 0 + - value: "Traces" + url: traces + depth: 0 + - value: "Alias" + url: alias + depth: 0 + - value: "Capture" + url: capture + depth: 0 + - value: "Capture AI" + url: capture-ai + depth: 0 + - value: "Capture exception" + url: capture-exception + depth: 0 + - value: "Emails" + url: emails + depth: 0 + - value: "Flush" + url: flush + depth: 0 + - value: "Get feature flag" + url: get-feature-flag + depth: 0 + - value: "Get feature flag payload" + url: get-feature-flag-payload + depth: 0 + - value: "Get feature flag result" + url: get-feature-flag-result + depth: 0 + - value: "Get feature flags" + url: get-feature-flags + depth: 0 + - value: "Get feature flags and payloads" + url: get-feature-flags-and-payloads + depth: 0 + - value: "Evaluate flags" + url: evaluate-flags + depth: 0 + - value: "Group identify" + url: group-identify + depth: 0 + - value: "Identify" + url: identify + depth: 0 + - value: "Is feature enabled" + url: is-feature-enabled + depth: 0 + - value: "Setup" + url: setup + depth: 0 + - value: "Shutdown" + url: shutdown + depth: 0 + - value: "Flag definition loader" + url: flag-definition-loader + depth: 0 + - value: "SDK utils" + url: sdk-utils + depth: 0 + - value: "AsyncClient" + url: asyncclient + depth: 0 + - value: "Module functions" + url: module-functions + depth: 0 + - value: "Types" + url: types + depth: 0 +--- + +## Getting started + + +The Python SDK captures events, evaluates feature flags and reports errors from Python 3.10 or later. + +### Install + +```bash +pip install posthog +``` + +### Initialize + +Create one client for your app with your project token and host. Your project token and host are in your [project settings](https://app.posthog.com/project/settings). Keep the token in an environment variable rather than in code. + +```python +from posthog import Posthog + +posthog = Posthog("", host="") +``` + +### Capture an event + +```python +posthog.capture("user_signed_up", distinct_id="user_123", properties={"plan": "free"}) +``` + +`capture` queues the event and returns right away. A background thread sends it. + +### Shut down + +Call `shutdown()` before your process exits, so queued events are sent. + +```python +posthog.shutdown() +``` + +In an asyncio app, use `AsyncPosthog` as an async context manager instead. See [AsyncClient](#asyncclient). + +## Traces + +### `Client.start_span` + +```python +start_span(name: str, *, kind: Optional[str] = None, attributes: Optional[Mapping[str, Any]] = None, parent: Union[Span, str, None] = None, tracestate: Optional[str] = None, start_time: Union[datetime, float, None] = None) -> Span +``` + +Start a span for distributed tracing. Alpha. + +Returns a span handle. Use it as a context manager to make it the active +span for the block and end it on exit (recording a raised exception on +the way out); or call `end()` yourself for a span that cannot wrap a +block. Spans started inside the block nest under it automatically. +Always returns a usable handle, even when tracing is off, so calling +code never branches. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`name` | str | A low-cardinality operation name, e.g. `GET /users/:id`. Variable values belong in attributes, not the name. | *required* +`kind` | Optional[str] | `internal` (default), `server`, `client`, `producer` or `consumer`. | None +`attributes` | Optional[Mapping[str, Any]] | Initial attributes. | None +`parent` | Union[Span, str, None] | A span handle, or an inbound W3C `traceparent` header value to continue a remote trace. Defaults to the active span. A forked child starts with no active span; pass the parent span to continue a trace across a fork. | None +`tracestate` | Optional[str] | The inbound `tracestate` header accompanying a `traceparent` string `parent`; preserved and propagated. | None +`start_time` | Union[datetime, float, None] | A `datetime` or epoch seconds, to backdate the span. | None + +**Examples:** + +```python +posthog = Posthog("", traces={"service_name": "checkout-api"}) + +with posthog.start_span("POST /checkout", parent=request.headers.get("traceparent")) as span: + span.set_attribute("plan", user.plan) + with posthog.start_span("db.query", kind="client"): + ... + outgoing_headers = {"traceparent": span.traceparent()} +``` + +### `Client.get_active_span` + +```python +get_active_span() -> Optional[Span] +``` + +The span that is active in the current context, or `None`. Alpha. + +Only entering a span (`with posthog.start_span(...) as span:`) makes +it active; a span started manually is not. Use it to propagate the +trace to the next service: `span.traceparent()` is the header value. + +**Examples:** + +```python +span = posthog.get_active_span() +if span is not None: + headers["traceparent"] = span.traceparent() +``` + +## Alias + +### `Client.alias` + +```python +alias(previous_id: ID_TYPES, distinct_id: Optional[str], timestamp: Optional[Union[datetime, str]] = None, uuid: Optional[str] = None, disable_geoip: Optional[bool] = None) -> Optional[str] +``` + +Create an alias between two distinct IDs. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`previous_id` | ID_TYPES | The previous distinct ID. Required - the call is dropped with a warning if it is missing or empty. | *required* +`distinct_id` | Optional[str] | The new distinct ID to alias to. Falls back to the context distinct ID; the call is dropped with a warning if neither is available. | *required* +`timestamp` | Optional[Union[datetime, str]] | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | None +`uuid` | Optional[str] | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | None +`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this event. | None + +**Examples:** + +```python +posthog.alias(previous_id='distinct_id', distinct_id='alias_id') +``` + +Note: This method will not raise exceptions. Errors are logged. + +## Capture + +### `Client.capture` + +```python +capture(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] +``` + +Captures an event manually. [Learn about capture best practices](https://posthog.com/docs/product-analytics/capture-events) + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`event` | str | The event name to capture. | *required* +`distinct_id` | | The distinct ID of the user. | +`properties` | | A dictionary of properties to include with the event. | +`timestamp` | | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | +`uuid` | | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | +`groups` | | A dictionary of group information. | +`flags` | | A FeatureFlagEvaluations snapshot from evaluate_flags(). The exact values from the snapshot are attached with no extra /flags request. | +`send_feature_flags` | | Deprecated. Prefer flags=... from evaluate_flags(). When truthy, evaluates flags during capture and attaches them to the event. | +`disable_geoip` | | Whether to disable GeoIP for this event. | + +**Examples:** + +```python +# Anonymous event +posthog.capture('some-anon-event') +``` +```python +# Context usage +from posthog import identify_context, new_context +with new_context(): + identify_context('distinct_id_of_the_user') + posthog.capture('user_signed_up') + posthog.capture('user_logged_in') + posthog.capture('some-custom-action', distinct_id='distinct_id_of_the_user') +``` +```python +# Set event properties +posthog.capture( + "user_signed_up", + distinct_id="distinct_id_of_the_user", + properties={ + "login_type": "email", + "is_free_trial": "true" + } +) +``` +```python +# Page view event +posthog.capture('$pageview', distinct_id="distinct_id_of_the_user", properties={'$current_url': 'https://example.com'}) +``` + +## Capture AI + +### `Client.capture_ai` + +```python +capture_ai(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] +``` + +Capture an AI event on the dedicated AI capture endpoint. + +Beta: the signature is stable; operational limits (per-event size +cap, batching, endpoint) may change without notice. + +Takes the same arguments and returns the same value as `capture()`: +the event UUID, or None when the event was not admitted (disabled +client, or dropped by `before_send`). The event is queued on an +isolated AI lane with its own consumer pool and a higher per-event +size cap, posting to the dedicated AI ingestion endpoint. The payload +is sent as given — no redaction or truncation is applied here. + +## Capture exception + +### `Client.capture_exception` + +```python +capture_exception(exception: Optional[ExceptionArg], **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] +``` + +Capture an exception for error tracking. + +When OpenTelemetry is installed and a valid span is active, its trace and +span IDs are added as `$trace_id` and `$span_id` event properties. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`exception` | Optional[ExceptionArg] | The exception to capture. | *required* +`distinct_id` | | The distinct ID of the user. | +`properties` | | A dictionary of additional properties. Overriding reserved exception properties is deprecated and will stop working in the next major version. | +`flags` | | A `FeatureFlagEvaluations` snapshot from `evaluate_flags()`. Attaches those exact flag values to the captured `$exception` event. | +`send_feature_flags` | | Deprecated. Pass `flags` from `evaluate_flags()` instead. | +`disable_geoip` | | Whether to disable GeoIP for this event. | + +**Examples:** + +```python +try: + # Some code that might fail + pass +except Exception as e: + posthog.capture_exception(e, 'user_distinct_id', properties=additional_properties) +``` + +## Emails + +### `Client.emails` + +```python +emails: Emails +``` + +Send transactional email from PostHog email templates, and read the +status of each email sent with `get`. + +Needs `secret_key`, a project secret API key with the `hog_flow:write` +scope. Sends go to `app_host`, or to the PostHog Cloud app host of +`host`. `email_request_timeout_seconds` and `email_request_max_retries` +configure each send. A disabled or shut-down client sends nothing. See +`posthog.emails` for setup. + +**Examples:** + +```python +client.emails.send( + to="user@example.com", + template_id="0190f6d2-7c1e-7b3a-9f5e-1c2d3e4f5a6b", + template_data={"name": "Ada"}, + distinct_id="user_123", +) +``` + +## Flush + +### `Client.flush` + +```python +flush(timeout_seconds: Optional[float] = 10) -> None +``` + +Attempt immediate delivery of queued events, ended spans and autocaptured metrics, keeping the client reusable. + +For a client shared across requests or serverless invocations (including +the module-level client), call `flush()` before a short-lived invocation +returns. Use `shutdown()` instead only for final process cleanup or when +disposing of a client that will not be reused. Flush does not stop the +background workers or feature flag poller, and does not end open spans. + +Returning does not guarantee server receipt: pending work can remain after +a timeout or span export failure, and failed events may be dropped by the +consumers. Calls made directly from SDK callbacks such as `on_error` are +deferred rather than blocking the worker that invoked the callback. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`timeout_seconds` | Optional[float] | Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass `None` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently. The span exporter waits up to this many seconds for an in-flight flush, then uses a fresh drain budget once it acquires the lock. If the lock wait times out, the in-flight flush is left to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. Each span request is bounded by the client's `timeout` in seconds, and the last request is not cut short. This is therefore not a strict wall-clock limit for the whole call. With `None`, a retriable span failure is left for a later flush rather than retried within this call. | 10 + +**Examples:** + +```python +posthog.capture('event_name', distinct_id='user_id') +# Before a serverless invocation returns; keep the shared client open. +posthog.flush(timeout_seconds=10) +``` + +## Get feature flag + +### `Client.get_feature_flag` + +```python +get_feature_flag(key: str, distinct_id: ID_TYPES, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[FlagValue] +``` + +Get multivariate feature flag value for a user. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The feature flag key. | *required* +`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None +`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None +`only_evaluate_locally` | bool | Whether to only evaluate locally. | False +`send_feature_flag_events` | bool | Whether to send feature flag events. | True +`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None +`device_id` | Optional[str] | The device ID for this request. | None + +**Examples:** + +```python +enabled_variant = posthog.get_feature_flag('flag-key', 'distinct_id_of_your_user') +if enabled_variant == 'variant-key': # replace 'variant-key' with the key of your variant + # Do something differently for this user + # Optional: fetch the payload + matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user') +``` + +## Get feature flag payload + +### `Client.get_feature_flag_payload` + +```python +get_feature_flag_payload(key: str, distinct_id: ID_TYPES, *, match_value: Optional[FlagValue] = None, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = False, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[object] +``` + +Get the payload for a feature flag. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The feature flag key. | *required* +`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* +`match_value` | Optional[FlagValue] | The specific flag value to get payload for. | None +`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None +`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None +`only_evaluate_locally` | bool | Whether to only evaluate locally. | False +`send_feature_flag_events` | bool | Deprecated. Use get_feature_flag() instead if you need events. | False +`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None +`device_id` | Optional[str] | The device ID for this request. | None + +**Returns:** + +Type | Description +---- | ----------- +Optional[object] | The payload associated with the matched feature flag value, or None. +Optional[object] | This method returns the payload only, not the FeatureFlagResult wrapper +Optional[object] | used internally to compute it. + +**Examples:** + +```python +is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user') + +if is_my_flag_enabled: + # Do something differently for this user + # Optional: fetch the payload + matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user') +``` + +## Get feature flag result + +### `Client.get_feature_flag_result` + +```python +get_feature_flag_result(key: str, distinct_id: ID_TYPES, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[FeatureFlagResult] +``` + +Get a FeatureFlagResult object which contains the flag result and payload for a key by evaluating locally or remotely +depending on whether local evaluation is enabled and the flag can be locally evaluated. +This also captures the `$feature_flag_called` event unless `send_feature_flag_events` is `False`. + +**Examples:** + +```python +flag_result = posthog.get_feature_flag_result('flag-key', 'distinct_id_of_your_user') +if flag_result and flag_result.get_value() == 'variant-key': + # Do something differently for this user + # Optional: fetch the payload + matched_flag_payload = flag_result.payload +``` + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The feature flag key. | *required* +`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None +`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None +`only_evaluate_locally` | bool | Whether to only evaluate locally. | False +`send_feature_flag_events` | bool | Whether to send feature flag events. | True +`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None +`device_id` | Optional[str] | The device ID for this request. | None + +**Returns:** + +Type | Description +---- | ----------- +Optional[FeatureFlagResult] | Optional[FeatureFlagResult]: The feature flag result or None if disabled/not found. + +## Get feature flags + +### `Client.get_feature_variants` + +```python +get_feature_variants(distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, disable_geoip: Optional[bool] = None, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None) -> dict[str, Union[bool, str]] +``` + +Get feature flag variants for a user. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None +`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None +`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None +`flag_keys_to_evaluate` | Optional[list[str]] | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | None +`device_id` | Optional[str] | The device ID for this request. | None + +### `Client.get_all_flags` + +```python +get_all_flags(distinct_id: ID_TYPES, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None) -> Optional[dict[str, Union[bool, str]]] +``` + +Get all feature flags for a user. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None +`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None +`only_evaluate_locally` | bool | Whether to only evaluate locally. | False +`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None +`flag_keys_to_evaluate` | Optional[list[str]] | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | None +`device_id` | Optional[str] | The device ID for this request. | None + +**Examples:** + +```python +posthog.get_all_flags('distinct_id_of_your_user') +``` + +## Get feature flags and payloads + +### `Client.get_feature_flags_and_payloads` + +```python +get_feature_flags_and_payloads(distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, disable_geoip: Optional[bool] = None, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None) -> FlagsAndPayloads +``` + +Get feature flags and payloads for a user. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None +`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None +`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None +`flag_keys_to_evaluate` | Optional[list[str]] | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | None +`device_id` | Optional[str] | The device ID for this request. | None + +**Examples:** + +```python +result = posthog.get_feature_flags_and_payloads('') +``` + +### `Client.get_all_flags_and_payloads` + +```python +get_all_flags_and_payloads(distinct_id: ID_TYPES, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None) -> FlagsAndPayloads +``` + +Get all feature flags and their payloads for a user. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None +`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None +`only_evaluate_locally` | bool | Whether to only evaluate locally. | False +`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None +`flag_keys_to_evaluate` | Optional[list[str]] | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | None +`device_id` | Optional[str] | The device ID for this request. | None + +**Examples:** + +```python +posthog.get_all_flags_and_payloads('distinct_id_of_your_user') +``` + +## Evaluate flags + +### `Client.evaluate_flags` + +```python +evaluate_flags(distinct_id: Optional[ID_TYPES] = None, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, flag_keys: Optional[List[str]] = None, device_id: Optional[str] = None) -> FeatureFlagEvaluations +``` + +Evaluate all feature flags for a user in a single call and return a +`FeatureFlagEvaluations` snapshot. Branch on `.is_enabled()` / +`.get_flag()` and pass the same snapshot to `capture` via the +`flags` option so events carry the exact flag values the code branched on. + +Prefer this over repeated `get_feature_flag()` calls and over +`capture(send_feature_flags=True)` — it consolidates flag evaluation into +a single `/flags` request per incoming request. + +Local evaluation is transparent: when the poller resolves a flag, the +snapshot's `$feature_flag_called` events are tagged `locally_evaluated=True` +and reason `"Evaluated locally"`. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | Optional[ID_TYPES] | The user's distinct ID. If `None`, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot. | None +`groups` | Optional[Mapping[str, Union[str, int]]] | Mapping of group type to group key. | None +`person_properties` | Optional[Dict[str, Any]] | Person properties to use for evaluation. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties keyed by group type. | None +`only_evaluate_locally` | bool | If True, never fall back to remote evaluation — flags that can't be evaluated locally are omitted from the snapshot and listed with a reason in its `unresolved_flags`. | False +`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup. | None +`flag_keys` | Optional[List[str]] | Optional list that scopes local evaluation, the underlying `/flags` request, and the returned snapshot. When omitted or `None`, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per `evaluate_flags` call unless `only_evaluate_locally` is True. If the server also does not know the key, it is omitted from the snapshot. | None +`device_id` | Optional[str] | Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes. | None + +**Returns:** + +Type | Description +---- | ----------- +FeatureFlagEvaluations | A `FeatureFlagEvaluations` snapshot. + +**Examples:** + +```python +flags = posthog.evaluate_flags( + "user_123", + person_properties={"plan": "enterprise"}, +) +if flags.is_enabled("new-dashboard"): + render_new_dashboard() +posthog.capture("page_viewed", distinct_id="user_123", flags=flags) +``` + +## Group identify + +### `Client.group_identify` + +```python +group_identify(group_type: str, group_key: str, properties: Optional[Dict[str, Any]] = None, timestamp: Optional[Union[datetime, str]] = None, uuid: Optional[Union[str, UUID]] = None, disable_geoip: Optional[bool] = None, distinct_id: Optional[ID_TYPES] = None) -> Optional[str] +``` + +Identify a group and set its properties. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`group_type` | str | The type of group (e.g., 'company', 'team'). Required - the call is dropped with a warning if it is missing or empty. | *required* +`group_key` | str | The unique identifier for the group. Required - the call is dropped with a warning if it is missing or empty. | *required* +`properties` | Optional[Dict[str, Any]] | A dictionary of properties to set on the group. | None +`timestamp` | Optional[Union[datetime, str]] | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | None +`uuid` | Optional[Union[str, UUID]] | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | None +`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this event. | None +`distinct_id` | Optional[ID_TYPES] | The distinct ID of the user performing the action. | None + +**Examples:** + +```python +posthog.group_identify('company', 'company_id_in_your_db', { + 'name': 'Awesome Inc.', + 'employees': 11 +}) +``` + +Note: This method will not raise exceptions. Errors are logged. + +## Identify + +### `Client.set` + +```python +set(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str] +``` + +Set properties on a person profile. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | | The distinct ID of the user. | +`properties` | | A dictionary of properties to set. | +`timestamp` | | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | +`uuid` | | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | +`disable_geoip` | | Whether to disable GeoIP for this event. | + +**Examples:** + +```python +# Set with distinct id +posthog.set(distinct_id='user123', properties={'name': 'Max Hedgehog'}) +``` + +Note: This method will not raise exceptions. Errors are logged. + +### `Client.set_once` + +```python +set_once(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str] +``` + +Set properties on a person profile only if they haven't been set before. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | | The distinct ID of the user. | +`properties` | | A dictionary of properties to set once. | +`timestamp` | | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | +`uuid` | | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | +`disable_geoip` | | Whether to disable GeoIP for this event. | + +**Examples:** + +```python +posthog.set_once(distinct_id='user123', properties={'initial_signup_date': '2024-01-01'}) +``` + +Note: This method will not raise exceptions. Errors are logged. + +## Is feature enabled + +### `Client.feature_enabled` + +```python +feature_enabled(key: str, distinct_id: ID_TYPES, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[bool] +``` + +Check if a feature flag is enabled for a user. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The feature flag key. | *required* +`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None +`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None +`only_evaluate_locally` | bool | Whether to only evaluate locally. | False +`send_feature_flag_events` | bool | Whether to send feature flag events. | True +`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None +`device_id` | Optional[str] | The device ID for this request. | None + +**Examples:** + +```python +is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user') +if is_my_flag_enabled: + # Do something differently for this user + # Optional: fetch the payload + matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user') +``` + +## Setup + +### `Client(...)` + +```python +__init__(project_api_key: str, host: str = None, debug: str = False, max_queue_size: str = 10000, send: str = True, on_error: str = None, flush_at: str = 100, flush_interval: str = 5.0, gzip: str = False, max_retries: str = 3, sync_mode: str = False, timeout: float = 15, thread: float = 1, poll_interval: float = 30, personal_api_key: float = None, disabled: float = False, disable_geoip: float = True, is_server: float = True, historical_migration: float = False, feature_flags_request_timeout_seconds: float = 3, feature_flags_request_max_retries: float = 1, super_properties: float = None, enable_exception_autocapture: float = False, log_captured_exceptions: float = False, project_root: float = None, privacy_mode: float = False, before_send: float = None, flag_fallback_cache_url: float = None, enable_local_evaluation: float = True, flag_definition_cache_provider: Optional[FlagDefinitionCacheProvider] = None, capture_exception_code_variables: Optional[FlagDefinitionCacheProvider] = False, code_variables_mask_patterns: Optional[FlagDefinitionCacheProvider] = None, code_variables_ignore_patterns: Optional[FlagDefinitionCacheProvider] = None, code_variables_mask_url_credentials: Optional[FlagDefinitionCacheProvider] = None, code_variables_detect_secrets: Optional[FlagDefinitionCacheProvider] = None, in_app_modules: list[str] | None = None, enable_exception_autocapture_rate_limiting: list[str] | None = False, exception_autocapture_bucket_size: list[str] | None = ExceptionCapture.DEFAULT_BUCKET_SIZE, exception_autocapture_refill_rate: list[str] | None = ExceptionCapture.DEFAULT_REFILL_RATE, exception_autocapture_refill_interval_seconds: list[str] | None = ExceptionCapture.DEFAULT_REFILL_INTERVAL_SECONDS, capture_mode: Optional[Union[CaptureMode, str]] = None, capture_compression: Optional[Union[CaptureCompression, str]] = None, secret_key: Optional[Union[CaptureCompression, str]] = None, metrics: Optional[dict] = None, enable_full_ai_capture: Optional[dict] = False, capture_trace_context: Optional[dict] = False, _use_ai_lane: Optional[dict] = False, _enable_multimodal_capture: Optional[dict] = False, traces: Optional[dict] = None, app_host: Optional[str] = None, email_request_timeout_seconds: float = 15, email_request_max_retries: int = 2) +``` + +Initialize a new PostHog client instance. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`project_api_key` | str | PostHog project API key/token. | *required* +`host` | | PostHog host. Defaults to the US ingestion endpoint when not set. App hosts such as `https://us.posthog.com` are mapped to the corresponding ingestion host. | None +`debug` | | Enable verbose SDK logging and re-raise errors from public API methods. | False +`max_queue_size` | | Maximum number of events buffered before upload. | 10000 +`send` | | If False, queueing succeeds but events are not sent. | True +`on_error` | | Optional callback invoked by background consumers when an upload fails. Keep it short and non-blocking. Calling lifecycle methods directly is safe and deferred, but do not start another thread or task that calls `flush()`, `join()`, or `shutdown()` and then wait for it from the callback. | None +`flush_at` | | Number of queued events that triggers a batch upload. | 100 +`flush_interval` | | Maximum seconds a background consumer waits before flushing a partial batch. | 5.0 +`gzip` | | Whether to gzip event upload payloads. | False +`max_retries` | | Number of upload retries. Values below 0 are treated as 0. | 3 +`sync_mode` | | If True, send each event synchronously instead of using background worker threads. This blocks the calling thread; in asyncio applications such as FastAPI, use `AsyncPosthog` instead. | False +`timeout` | float | HTTP request timeout in seconds for event uploads. | 15 +`thread` | | Number of background consumer threads. | 1 +`poll_interval` | | Seconds between local feature flag definition refreshes. | 30 +`secret_key` | | A Personal API Key or Project Secret API Key, used to authenticate local feature flag evaluation, remote config payloads, and decrypted flag payloads. Example::
posthog.Client(project_api_key, secret_key="phx_...") | None +`personal_api_key` | | Deprecated alias for `secret_key`. Still honored for backwards compatibility; prefer `secret_key`, which also accepts a Project Secret API Key. | None +`disabled` | | If True, disable captures and API requests. Useful in tests. | False +`disable_geoip` | | Whether to disable server-side GeoIP enrichment. Defaults to True. | True +`is_server` | | Whether events are emitted from a server-side runtime. Defaults to True; set to False when using the SDK as a client/CLI so the device OS is attributed to the person normally. | True +`historical_migration` | | Mark events as historical migration imports. | False +`feature_flags_request_timeout_seconds` | float | Timeout in seconds for feature flag and remote config requests. | 3 +`feature_flags_request_max_retries` | | Number of retries for feature flag requests after network, transport, or timeout failures. Defaults to 1. Set to 0 to disable retries. | 1 +`app_host` | Optional[str] | Your PostHog app host, such as `https://posthog.example.com`. `emails` sends there. Needed only when `host` is not PostHog Cloud, for example a reverse proxy, which must not receive the secret key. | None +`email_request_timeout_seconds` | float | Timeout in seconds for each email send request. Defaults to 15. | 15 +`email_request_max_retries` | int | Number of retries for an email send after a network error, a rate limit, or an unavailable service. Defaults to 2. Set to 0 when a job runner already retries the send. | 2 +`super_properties` | | Properties merged into every captured event. | None +`enable_exception_autocapture` | | Automatically capture uncaught exceptions. | False +`log_captured_exceptions` | | Also log exceptions captured by error tracking. | False +`project_root` | | Root path used to determine in-app stack frames for captured exceptions. Defaults to the current working directory. | None +`privacy_mode` | | For AI observability, capture usage metadata without prompt inputs or outputs. | False +`enable_full_ai_capture` | | Route PostHog AI wrapper events through the dedicated AI capture endpoint and capture full AI content: skips string truncation and passes media (base64/data URIs) through unredacted. `privacy_mode` always wins. Defaults to False. | False +`before_send` | | Optional callback that can modify or drop events before upload. Return `None` to drop an event. | None +`flag_fallback_cache_url` | | Optional feature flag fallback cache URL, such as `memory://local/?ttl=300&size=10000` or a Redis URL. | None +`enable_local_evaluation` | | Whether to poll feature flag definitions for local evaluation when a personal API key is configured. | True +`flag_definition_cache_provider` | Optional[FlagDefinitionCacheProvider] | Optional external cache provider for sharing feature flag definitions across workers. | None +`capture_exception_code_variables` | | Capture local variable values on exception stack frames. | False +`capture_trace_context` | | When OpenTelemetry is installed and a valid span is active at capture time, add its trace and span IDs as `$trace_id` and `$span_id` properties to events captured with `capture()` and `capture_ai()`, so they can be correlated with backend traces. Explicit `$trace_id`/`$span_id` values passed in `properties` win. Exception events (`capture_exception`) always attach these IDs regardless of this setting. Defaults to False. | False +`traces` | Optional[dict] | Config dict for distributed tracing: `service_name`, `service_version`, `environment`, `resource_attributes`, `flush_interval` (5 s), `max_queue_size` (2048), `max_export_batch_size` (512), `max_live_spans` (10000), `max_span_age` (3600 s), `max_attributes_per_span` (128), `max_events_per_span` (128), `max_attribute_value_length` (8192). `before_span_send` is a callable, or a list run in order, that receives each finished span as a dict (`trace_id`, `span_id` and `parent_span_id` are read-only) and returns it, edited, or `None` to drop it; a hook that raises drops the span. Tracing is off until this is provided. Spans export on a background timer even with `sync_mode`; serverless handlers should call `flush()` before returning. Defaults to None. | None +`code_variables_mask_patterns` | | Variable-name patterns to mask when capturing code variables. | None +`code_variables_ignore_patterns` | | Variable-name patterns to omit when capturing code variables. | None +`code_variables_mask_url_credentials` | | Scrub credentials embedded in URLs/DSNs (e.g. `user:pass@host`) from captured code variables, regardless of the surrounding variable name. Defaults to True. | None +`code_variables_detect_secrets` | | Last-resort entropy-based detection that redacts high-entropy secret-looking values (API keys, tokens, strong passwords) sitting in innocuously-named variables, after the name and URL checks. Skips structured ids (UUIDs, ObjectIds, hashes). Defaults to True. | None +`in_app_modules` | list[str] \| None | Module/package prefixes treated as in-app frames in captured exceptions. | None +`enable_exception_autocapture_rate_limiting` | | Rate limit autocaptured exceptions client-side with a token bucket per exception type. Disabled by default. | False +`exception_autocapture_bucket_size` | | Maximum burst of autocaptured exceptions allowed per exception type (token bucket size, clamped to 0-100). | DEFAULT_BUCKET_SIZE +`exception_autocapture_refill_rate` | | Tokens restored per refill interval for each exception type's bucket. | DEFAULT_REFILL_RATE +`exception_autocapture_refill_interval_seconds` | | Seconds between token refills for autocaptured exception rate limiting. | DEFAULT_REFILL_INTERVAL_SECONDS +`capture_mode` | Optional[Union[CaptureMode, str]] | Capture wire protocol to use. Defaults to `CaptureMode.V0` (legacy `/batch/`). Set `CaptureMode.V1` (or pass the string `"v1"`) to opt into `/i/v1/analytics/events`. When omitted, the `POSTHOG_CAPTURE_MODE` env var is consulted, then `V0`. | None +`capture_compression` | Optional[Union[CaptureCompression, str]] | Request-body compression for capture-v1 uploads (ignored in V0, which uses `gzip`). `CaptureCompression.GZIP` or `DEFLATE` (or the strings `"gzip"`/`"deflate"`). When omitted, the `POSTHOG_CAPTURE_COMPRESSION` env var is consulted, then the legacy `gzip` flag, then no compression. | None + +**Examples:** + +```python +from posthog import Posthog + +posthog = Posthog('', host='') +``` + +## Shutdown + +### `Client.join` + +```python +join() -> None +``` + +Attempt to process queued events and end the consumer threads. Do not use directly, call `shutdown()` instead. + +Failed or undrainable events may be dropped and reported through logging +or `on_error`; returning does not guarantee server receipt. Lifecycle +cleanup is attempted once, and cleanup failures are logged without retry. + +**Examples:** + +```python +posthog.join() +``` + +### `Client.shutdown` + +```python +shutdown() -> None +``` + +Attempt final delivery and permanently shut down the client. + +Call this for final process cleanup or when disposing of a client that +will not be reused. It closes capture lanes, stops background workers and +the feature flag poller, and tears down integrations. Do not call it at the +end of each request or serverless invocation when the client is shared; +call `flush()` before returning instead. A shut-down client cannot be +reused; create a new client if needed. + +Normally this method blocks until queued events have been attempted and +cleanup finishes. Failed or undrainable events may be dropped and +reported through logging or `on_error`; returning does not guarantee +server receipt. Event draining has no timeout. Queued spans get one final +flush with up to 30 seconds waiting for an in-flight flush, then a fresh +30-second drain budget; the last HTTP request is not cut short. Any spans +it cannot send are discarded with a warning, as are spans still open. +Lifecycle cleanup is attempted once, and cleanup failures +are logged without retry. When called directly from an SDK callback such as +`on_error`, shutdown is deferred to avoid blocking the worker that +invoked the callback. If the callback must coordinate a blocking +shutdown, have it signal an +application-owned thread and return before that thread calls shutdown. +Do not wait inside the callback for another thread or task that calls a +lifecycle method. + +**Examples:** + +```python +posthog.shutdown() +``` + +## Flag definition loader + +### `Client.load_feature_flags` + +```python +load_feature_flags() +``` + +Load feature flags for local evaluation. + +**Examples:** + +```python +posthog.load_feature_flags() +``` + +### `Client.feature_flag_definitions` + +```python +feature_flag_definitions() +``` + +Return feature flag definitions loaded for local evaluation. + +**Returns:** + +Type | Description +---- | ----------- + | The currently loaded feature flag definitions, or `None` before + | local evaluation has loaded definitions. + +## SDK utils + +### `Client.queue` + +```python +queue: Queue +``` + +The analytics lane's queue (kept for backwards compatibility). + +### `Client.consumers` + +```python +consumers: Optional[List[Consumer]] +``` + +Flat list of the lanes' consumers, analytics first (kept for backwards compatibility). + +### `Client.new_context` + +```python +new_context(fresh = False, capture_exceptions: Optional[bool] = None) +``` + +Create a new context for managing shared state. Learn more about [contexts](/docs/libraries/python#contexts). + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`fresh` | | Whether to create a fresh context that doesn't inherit from parent. | False +`capture_exceptions` | Optional[bool] | Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting. | None + +**Examples:** + +```python +with client.new_context(): + client.identify_context('') + client.capture('event_name') +``` + +
+Category + +Contexts + +
+ +### `Client.scoped` + +```python +scoped(fresh = False, capture_exceptions: Optional[bool] = None) +``` + +Decorator that creates a new context for the wrapped function using this client. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`fresh` | | Whether to create a fresh context that doesn't inherit from parent. | False +`capture_exceptions` | Optional[bool] | Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting. | None + +
+Category + +Contexts + +
+ +### `Client.tag` + +```python +tag(name: str, value: Any) -> None +``` + +Add a tag to the current context. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`name` | str | The tag key. | *required* +`value` | Any | The tag value. | *required* + +
+Category + +Contexts + +
+ +### `Client.get_tags` + +```python +get_tags() -> Dict[str, Any] +``` + +Get all tags from the current context. + +**Returns:** + +Type | Description +---- | ----------- +Dict[str, Any] | Dict of all tags in the current context. + +
+Category + +Contexts + +
+ +### `Client.identify_context` + +```python +identify_context(distinct_id: str) -> None +``` + +Identify the current context with a distinct ID. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | str | The distinct ID to associate with the current context and its children. | *required* + +
+Category + +Contexts + +
+ +### `Client.set_context_session` + +```python +set_context_session(session_id: str) -> None +``` + +Set the session ID for the current context. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`session_id` | str | The session ID to associate with the current context and its children. | *required* + +
+Category + +Contexts + +
+ +### `Client.set_context_device_id` + +```python +set_context_device_id(device_id: str) -> None +``` + +Set the device ID for the current context. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`device_id` | str | The device ID to associate with the current context and its children. | *required* + +
+Category + +Contexts + +
+ +### `Client.feature_flags` + +```python +feature_flags +``` + +Get the local evaluation feature flags. + +### `Client.get_feature_payloads` + +```python +get_feature_payloads(distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, disable_geoip: Optional[bool] = None, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None) -> dict[str, Optional[str]] +``` + +Get feature flag payloads for a user, preserving valid serialized JSON. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None +`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None +`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None +`flag_keys_to_evaluate` | Optional[list[str]] | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | None +`device_id` | Optional[str] | The device ID for this request. | None + +**Examples:** + +```python +payloads = posthog.get_feature_payloads('') +``` + +
+Category + +Feature flags + +
+ +### `Client.get_flags_decision` + +```python +get_flags_decision(distinct_id: Optional[ID_TYPES] = None, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, disable_geoip: Optional[bool] = None, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None) -> FlagsResponse +``` + +Get feature flags decision. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | Optional[ID_TYPES] | The distinct ID of the user. | None +`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None +`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None +`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None +`flag_keys_to_evaluate` | Optional[list[str]] | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | None +`device_id` | Optional[str] | The device ID for this request. | None + +**Examples:** + +```python +decision = posthog.get_flags_decision('user123') +``` + +
+Category + +Feature flags + +
+ +### `Client.metrics` + +```python +metrics: PostHogMetrics +``` + +The `posthog.metrics` API: a statsd-style pre-aggregating metrics client — alpha. + +Samples fold into per-series aggregates in memory and flush as one OTLP +data point per series per window, so recording from hot paths is cheap. +Configure via the `metrics` client option; pending metrics flush on +`shutdown()`. + +**Examples:** + +```python +client = Posthog("", metrics={"service_name": "billing-worker"}) +client.metrics.count("invoices.processed", 1, attributes={"plan": "pro"}) +client.metrics.gauge("queue.depth", 42) +client.metrics.histogram("job.duration", 187, unit="ms") +``` + +### `Client.get_remote_config_payload` + +```python +get_remote_config_payload(key: str) +``` + +Get the payload for a remote config feature flag. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The remote config feature flag key. | *required* + +**Returns:** + +Type | Description +---- | ----------- + | The payload associated with the feature flag, or `None` if the + | client is disabled, no personal API key is configured, or the request + | fails. Encrypted payloads are decrypted by PostHog before being + | returned. + +
+Note + +Requires `secret_key` for authentication. + +
+ +
+Category + +Feature flags + +
+ +### `Client.get_feature_flag_evaluation_runtime` + +```python +get_feature_flag_evaluation_runtime(key: str) -> Optional[FeatureFlagEvaluationRuntime] +``` + +Return where a locally loaded feature flag is meant to be evaluated. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The feature flag key. | *required* + +**Returns:** + +Type | Description +---- | ----------- +Optional[FeatureFlagEvaluationRuntime] | The flag's evaluation runtime, or `None` when local evaluation has +Optional[FeatureFlagEvaluationRuntime] | not loaded a definition for this key. A definition that carries no +Optional[FeatureFlagEvaluationRuntime] | runtime reports `FeatureFlagEvaluationRuntime.ALL`, the default +Optional[FeatureFlagEvaluationRuntime] | PostHog applies. + +**Examples:** + +```python +from posthog import FeatureFlagEvaluationRuntime + +runtime = posthog.get_feature_flag_evaluation_runtime("my-flag") +if runtime is FeatureFlagEvaluationRuntime.SERVER: + ... +``` + +
+Category + +Feature flags + +
+ +### `Client.get_feature_flag_keys_by_evaluation_runtime` + +```python +get_feature_flag_keys_by_evaluation_runtime(evaluation_runtime: Union[FeatureFlagEvaluationRuntime, str]) -> list[str] +``` + +Return the keys of locally loaded flags that a runtime can evaluate. + +A flag set to `FeatureFlagEvaluationRuntime.ALL` suits either runtime, +so it is returned for `CLIENT` and for `SERVER`, and asking for +`ALL` returns every loaded flag. Use this to decide which flags to hand +to a browser when a backend serves flags to its own frontend. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`evaluation_runtime` | Union[FeatureFlagEvaluationRuntime, str] | The runtime to match, as a `FeatureFlagEvaluationRuntime` or its string value. | *required* + +**Returns:** + +Type | Description +---- | ----------- +list[str] | The matching flag keys, in the order local evaluation loaded them. +list[str] | Empty when no definitions are loaded. + +**Examples:** + +```python +from posthog import FeatureFlagEvaluationRuntime + +client_keys = posthog.get_feature_flag_keys_by_evaluation_runtime( + FeatureFlagEvaluationRuntime.CLIENT +) +``` + +
+Category + +Feature flags + +
+ +### `posthog.integrations.django.PosthogContextMiddleware` + +```python +PosthogContextMiddleware(get_response) +``` + +Middleware to automatically track Django requests. + +This middleware wraps all calls with a posthog context. It attempts to extract the following from the request: +- Session ID, (extracted from `X-POSTHOG-SESSION-ID`) +- Distinct ID, (extracted from `X-POSTHOG-DISTINCT-ID`, falling back to the authenticated request user ID) +- Authenticated user email as `email` +- Request URL as `$current_url` +- Request method as `$request_method` +- Request path as `$request_path` +- Forwarded IP address as `$ip` +- User agent as `$user_agent` + +Configure the integration with these Django settings: +- `POSTHOG_MW_CLIENT`: client used for automatic exception capture, or the global client. + This does not redirect events explicitly captured through other clients. +- `POSTHOG_MW_CAPTURE_EXCEPTIONS`: `False` enables context enrichment only; + `True` enables automatic exception capture; `None` inherits the effective client's + `enable_exception_autocapture`. When omitted, the legacy default is `True`. +- `POSTHOG_MW_REQUEST_FILTER`: a callback receiving the request. Returning `False` + disables both enrichment and automatic exception capture for that request. +- `POSTHOG_MW_EXTRA_PROPERTIES`: a callback returning additional event properties. +- `POSTHOG_MW_CAPTURE_TRACING_HEADERS`: read PostHog identity and session headers + as request-scoped analytics context (default: `True`). Explicit capture IDs and + event properties take precedence. These values are never authentication or + authorization. Authenticated Django user identity remains the fallback when no + usable header ID is available or header capture is disabled. +- `POSTHOG_MW_TRUST_TRACING_HEADERS`: deprecated alias for + `POSTHOG_MW_CAPTURE_TRACING_HEADERS`; the capture-named setting takes precedence. +- `POSTHOG_MW_PROPERTIES_MAP`: an optional callback transforming request properties. + +The old `POSTHOG_MW_EXTRA_TAGS` and `POSTHOG_MW_TAG_MAP` settings remain aliases; +the property-named settings take precedence when both are configured. + +Request properties are included on all events captured within the active context, +including when automatic exception capture is disabled. +See the context documentation for more information. The extracted distinct ID and session ID, +if found, are used to associate all events captured in the middleware context with the same distinct ID +and session as currently active on the frontend. See the documentation for `set_context_session` +and `identify_context` for more details. + +This middleware is hybrid-capable: it supports both WSGI (sync) and ASGI (async) Django applications. The middleware +detects at initialization whether the next middleware in the chain is async or sync, and adapts its behavior accordingly. +This ensures compatibility with both pure sync and pure async middleware chains, as well as mixed chains in ASGI mode. + +**Functions:** + +Name | Description +---- | ----------- +`aextract_properties` | Async version of extract_properties for use in async request handling. +`aextract_request_user` | Async version of extract_request_user for use in async request handling. +`aextract_tags` | Compatibility alias for `aextract_properties`. +`extract_properties` | Extract event properties and identity from a synchronous request. +`extract_request_user` | Extract user ID and email from request in sync context. +`extract_tags` | Compatibility alias for `extract_properties`. +`process_exception` | Process exceptions from views and downstream middleware. + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`async_capable` | | +`capture_exceptions` | Optional[bool] | +`capture_tracing_headers` | | +`client` | | +`extra_properties` | | +`extra_tags` | | Compatibility alias for `extra_properties`. +`get_response` | | +`properties_map` | | +`request_filter` | | +`sync_capable` | | +`tag_map` | | Compatibility alias for `properties_map`. +`trust_tracing_headers` | bool | Deprecated alias for `capture_tracing_headers`. + +Initialize the middleware with Django's next handler. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`get_response` | | The next middleware or view handler in Django's middleware chain. May be synchronous or asynchronous. | + +#### `posthog.integrations.django.PosthogContextMiddleware.aextract_properties` + +```python +aextract_properties(request) +``` + +Async version of extract_properties for use in async request handling. + +Uses await request.auser() instead of request.user to avoid +SynchronousOnlyOperation in async context. + +Follows Django's naming convention for async methods (auser, asave, etc.). + +#### `posthog.integrations.django.PosthogContextMiddleware.aextract_request_user` + +```python +aextract_request_user(request) +``` + +Async version of extract_request_user for use in async request handling. + +Uses await request.auser() instead of request.user to avoid +SynchronousOnlyOperation in async context. + +Follows Django's naming convention for async methods (auser, asave, etc.). + +#### `posthog.integrations.django.PosthogContextMiddleware.aextract_tags` + +```python +aextract_tags(request) +``` + +Compatibility alias for `aextract_properties`. + +#### `posthog.integrations.django.PosthogContextMiddleware.async_capable` + +```python +async_capable = True +``` + +#### `posthog.integrations.django.PosthogContextMiddleware.capture_exceptions` + +```python +capture_exceptions: Optional[bool] = capture_exceptions if (capture_exceptions is None or isinstance(capture_exceptions, bool)) else True +``` + +#### `posthog.integrations.django.PosthogContextMiddleware.capture_tracing_headers` + +```python +capture_tracing_headers = _resolve_capture_tracing_headers(capture_tracing_headers, trust_tracing_headers, canonical_name='POSTHOG_MW_CAPTURE_TRACING_HEADERS', deprecated_name='POSTHOG_MW_TRUST_TRACING_HEADERS') +``` + +#### `posthog.integrations.django.PosthogContextMiddleware.client` + +```python +client = cast('Optional[Client]', settings.POSTHOG_MW_CLIENT) +``` + +#### `posthog.integrations.django.PosthogContextMiddleware.extra_properties` + +```python +extra_properties = cast('Callable[[HttpRequest], Dict[str, Any]]', extra_properties) if callable(extra_properties) else None +``` + +#### `posthog.integrations.django.PosthogContextMiddleware.extra_tags` + +```python +extra_tags +``` + +Compatibility alias for `extra_properties`. + +#### `posthog.integrations.django.PosthogContextMiddleware.extract_properties` + +```python +extract_properties(request) +``` + +Extract event properties and identity from a synchronous request. + +#### `posthog.integrations.django.PosthogContextMiddleware.extract_request_user` + +```python +extract_request_user(request) +``` + +Extract user ID and email from request in sync context. + +#### `posthog.integrations.django.PosthogContextMiddleware.extract_tags` + +```python +extract_tags(request) +``` + +Compatibility alias for `extract_properties`. + +#### `posthog.integrations.django.PosthogContextMiddleware.get_response` + +```python +get_response = get_response +``` + +#### `posthog.integrations.django.PosthogContextMiddleware.process_exception` + +```python +process_exception(request, exception) +``` + +Process exceptions from views and downstream middleware. + +Django calls this WHILE still inside the context created by __call__, +so request tags have already been extracted and set. This method just +needs to capture the exception directly. + +Django converts view exceptions into responses before they propagate through +the middleware stack, so the context manager in __call__/__acall__ never sees them. + +Note: Django's process_exception is always synchronous, even for async views. + +#### `posthog.integrations.django.PosthogContextMiddleware.properties_map` + +```python +properties_map = cast('Callable[[Dict[str, Any]], Dict[str, Any]]', properties_map) if callable(properties_map) else None +``` + +#### `posthog.integrations.django.PosthogContextMiddleware.request_filter` + +```python +request_filter = cast('Optional[Callable[[HttpRequest], bool]]', settings.POSTHOG_MW_REQUEST_FILTER) +``` + +#### `posthog.integrations.django.PosthogContextMiddleware.sync_capable` + +```python +sync_capable = True +``` + +#### `posthog.integrations.django.PosthogContextMiddleware.tag_map` + +```python +tag_map +``` + +Compatibility alias for `properties_map`. + +#### `posthog.integrations.django.PosthogContextMiddleware.trust_tracing_headers` + +```python +trust_tracing_headers: bool +``` + +Deprecated alias for `capture_tracing_headers`. + +### `posthog.integrations.flask.PosthogFlaskIntegration` + +```python +PosthogFlaskIntegration(app: Optional[Flask] = None, *, client: Optional[Client] = None, capture_exceptions: Optional[bool] = None, request_filter: Optional[Callable[[Request], bool]] = None, extra_properties: Optional[Callable[[Request], Mapping[str, Any]]] = None, trust_tracing_headers: Optional[bool] = None, capture_tracing_headers: Optional[bool] = None) -> None +``` + +Add PostHog request context and exception tracking to a Flask app. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`app` | Optional[Flask] | An optional Flask application. If omitted, call `init_app` later (the Flask application-factory pattern). | None +`client` | Optional[Client] | Optional destination for automatically captured exceptions. The global client is used by default. Request context still enriches events captured through any client and does not redirect capture calls. | None +`capture_exceptions` | Optional[bool] | Capture exceptions that reach Flask's unhandled exception machinery. If omitted, inherit the effective client's `enable_exception_autocapture` setting. Pass `True` or `False` to override it. | None +`request_filter` | Optional[Callable[[Request], bool]] | Optional callback receiving Flask's request object. A false return value disables both context and exception capture for that request. | None +`extra_properties` | Optional[Callable[[Request], Mapping[str, Any]]] | Optional callback returning additional event properties. This is useful for application-specific metadata such as an authenticated user's role. Values should not contain secrets or request bodies. | None +`capture_tracing_headers` | Optional[bool] | Read PostHog distinct and session ID headers as request-scoped analytics context. Defaults to True. Explicit capture IDs and event properties take precedence. Never use these values for security. | None +`trust_tracing_headers` | Optional[bool] | Deprecated alias for `capture_tracing_headers`. The capture-named option takes precedence when both are supplied. | None + +The integration intentionally does not capture exceptions handled by an +application error handler, expected HTTP exceptions, request or response +bodies, query strings, cookies, authorization headers, or arbitrary headers. +Call `capture_exception` explicitly from a custom error handler if a +handled exception should be reported. + +**Functions:** + +Name | Description +---- | ----------- +`init_app` | Register the integration with a Flask application once. + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`capture_exceptions` | | +`capture_tracing_headers` | | +`client` | | +`extra_properties` | | +`request_filter` | | +`trust_tracing_headers` | bool | Deprecated alias for `capture_tracing_headers`. + +#### `posthog.integrations.flask.PosthogFlaskIntegration.capture_exceptions` + +```python +capture_exceptions = capture_exceptions +``` + +#### `posthog.integrations.flask.PosthogFlaskIntegration.capture_tracing_headers` + +```python +capture_tracing_headers = _resolve_capture_tracing_headers(capture_tracing_headers, trust_tracing_headers) +``` + +#### `posthog.integrations.flask.PosthogFlaskIntegration.client` + +```python +client = client +``` + +#### `posthog.integrations.flask.PosthogFlaskIntegration.extra_properties` + +```python +extra_properties = extra_properties +``` + +#### `posthog.integrations.flask.PosthogFlaskIntegration.init_app` + +```python +init_app(app: Flask) -> None +``` + +Register the integration with a Flask application once. + +#### `posthog.integrations.flask.PosthogFlaskIntegration.request_filter` + +```python +request_filter = request_filter +``` + +#### `posthog.integrations.flask.PosthogFlaskIntegration.trust_tracing_headers` + +```python +trust_tracing_headers: bool +``` + +Deprecated alias for `capture_tracing_headers`. + +### `posthog.integrations.asgi.PosthogASGIMiddleware` + +```python +PosthogASGIMiddleware(app: _ASGIApp, client: Optional[Client] = None, capture_exceptions: Optional[bool] = None, request_filter: Optional[_RequestFilter] = None, extra_properties: Optional[_ExtraProperties] = None, trust_tracing_headers: Optional[bool] = None, *, capture_tracing_headers: Optional[bool] = None) -> None +``` + +Add PostHog context and automatic exception capture to an ASGI app. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`app` | _ASGIApp | The downstream ASGI application. | *required* +`client` | Optional[Client] | Optional destination for automatically captured exceptions. The global client is used by default. Request context still enriches events captured through any client and does not redirect capture calls. | None +`capture_exceptions` | Optional[bool] | Capture exceptions escaping the downstream app. If omitted, inherit the effective client's `enable_exception_autocapture` setting. Pass `True` or `False` to override it. | None +`request_filter` | Optional[_RequestFilter] | Optional sync or async callback receiving the ASGI scope. Returning `False` bypasses all instrumentation for that scope. | None +`extra_properties` | Optional[_ExtraProperties] | Optional sync or async callback receiving the ASGI scope and returning additional event properties. | None +`capture_tracing_headers` | Optional[bool] | Read PostHog distinct and session ID headers as request-scoped analytics context. Defaults to True. Explicit capture IDs and event properties take precedence. Never use these values for security. | None +`trust_tracing_headers` | Optional[bool] | Deprecated alias for `capture_tracing_headers`. The capture-named option takes precedence when both are supplied. | None + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`app` | | +`capture_exceptions` | | +`capture_tracing_headers` | | +`client` | | +`extra_properties` | | +`request_filter` | | +`trust_tracing_headers` | bool | Deprecated alias for `capture_tracing_headers`. + +#### `posthog.integrations.asgi.PosthogASGIMiddleware.app` + +```python +app = app +``` + +#### `posthog.integrations.asgi.PosthogASGIMiddleware.capture_exceptions` + +```python +capture_exceptions = capture_exceptions +``` + +#### `posthog.integrations.asgi.PosthogASGIMiddleware.capture_tracing_headers` + +```python +capture_tracing_headers = _resolve_capture_tracing_headers(capture_tracing_headers, trust_tracing_headers) +``` + +#### `posthog.integrations.asgi.PosthogASGIMiddleware.client` + +```python +client = client +``` + +#### `posthog.integrations.asgi.PosthogASGIMiddleware.extra_properties` + +```python +extra_properties = extra_properties +``` + +#### `posthog.integrations.asgi.PosthogASGIMiddleware.request_filter` + +```python +request_filter = request_filter +``` + +#### `posthog.integrations.asgi.PosthogASGIMiddleware.trust_tracing_headers` + +```python +trust_tracing_headers: bool +``` + +Deprecated alias for `capture_tracing_headers`. + +### `posthog.integrations.celery.PosthogCeleryIntegration` + +```python +PosthogCeleryIntegration(client: Optional[Client] = None, capture_exceptions: bool = True, capture_task_lifecycle_events: bool = True, propagate_context: bool = True, task_filter: Optional[Callable[[Optional[str], dict[str, Any]], bool]] = None) +``` + +Celery integration that captures task lifecycle events and exceptions. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`client` | Optional[Client] | Optional `Client` instance. When provided, all events and exceptions are captured through this client rather than the global `posthog` module. Don't skip this if using a custom flag definition cache provider, and pass the custom `Client` instance here initialized with the custom provider so fork safety for that provider is handled correctly. | None +`capture_exceptions` | bool | Whether to capture task exceptions via `capture_exception` (default `True`). | True +`capture_task_lifecycle_events` | bool | Whether to emit lifecycle events of the task such as "started", "success", "failure" etc. (default `True`). | True +`propagate_context` | bool | Whether to propagate PostHog context (distinct ID, session ID, tags) from the producer to the worker via task headers (default `True`). | True +`task_filter` | Optional[Callable[[Optional[str], dict[str, Any]], bool]] | Optional callback `(task_name, task_properties) -> bool` expected to return `False` if a given task should not be tracked. | None + +**Functions:** + +Name | Description +---- | ----------- +`instrument` | Connect Celery signal handlers to capture task events and exceptions. +`shutdown` | Disconnect all signal handlers registered by `instrument()`, flush all pending events +`uninstrument` | Disconnect Celery signal handlers and unregister exit cleanup. + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`capture_exceptions` | | +`capture_task_lifecycle_events` | | +`client` | | +`propagate_context` | | +`task_filter` | | + +#### `posthog.integrations.celery.PosthogCeleryIntegration.capture_exceptions` + +```python +capture_exceptions = capture_exceptions +``` + +#### `posthog.integrations.celery.PosthogCeleryIntegration.capture_task_lifecycle_events` + +```python +capture_task_lifecycle_events = capture_task_lifecycle_events +``` + +#### `posthog.integrations.celery.PosthogCeleryIntegration.client` + +```python +client = client +``` + +#### `posthog.integrations.celery.PosthogCeleryIntegration.instrument` + +```python +instrument() -> None +``` + +Connect Celery signal handlers to capture task events and exceptions. +Call this after initializing the PostHog client and this integration. + +If Celery runs on a single host, reinstrumenting in worker children is +not strictly necessary because the PostHog client and this integration +are fork-safe. If Celery workers run across multiple hosts, each worker +process must initialize PostHog, this integration, and call +`instrument()`. Celery provides `worker_process_init` signal to help +with this. + +#### `posthog.integrations.celery.PosthogCeleryIntegration.propagate_context` + +```python +propagate_context = propagate_context +``` + +#### `posthog.integrations.celery.PosthogCeleryIntegration.shutdown` + +```python +shutdown() -> None +``` + +Disconnect all signal handlers registered by `instrument()`, flush all pending events +and cleanly shutdown the integration. + +`shutdown()` is also registered on `worker_process_shutdown` and `atexit` signals, +but there is no guarantee those will always be called, so we strongly recommend calling +it manually when the integration is no longer needed to avoid data loss. + +#### `posthog.integrations.celery.PosthogCeleryIntegration.task_filter` + +```python +task_filter = task_filter +``` + +#### `posthog.integrations.celery.PosthogCeleryIntegration.uninstrument` + +```python +uninstrument() -> None +``` + +Disconnect Celery signal handlers and unregister exit cleanup. + +Do not use directly, call `shutdown()` instead. + +### `posthog.request.set_socket_options` + +```python +set_socket_options(socket_options: Optional[SocketOptions]) -> None +``` + +Configure socket options for all SDK HTTP connections. + +Call this during initialization, before making API requests. Pass `None` +to reset to the default socket behavior. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`socket_options` | Optional[SocketOptions] | A list of `(level, option, value)` tuples accepted by urllib3/`socket.setsockopt()`, or `None` to reset defaults. | *required* + +
+Example + +from posthog import set_socket_options +set_socket_options([(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)]) + +
+ +### `posthog.request.enable_keep_alive` + +```python +enable_keep_alive() -> None +``` + +Enable TCP keepalive for SDK HTTP connections. + +This helps prevent idle pooled connections from being dropped by network +infrastructure. Call during initialization, before making API requests. + +### `posthog.request.disable_connection_reuse` + +```python +disable_connection_reuse() -> None +``` + +Disable HTTP connection reuse for SDK requests. + +Each request will create a fresh connection. This can avoid issues with +environments that terminate pooled connections, but adds per-request +overhead. Call during initialization, before making API requests. + +## AsyncClient + +Needs the `async` extra, `posthog[async]`. + +### `AsyncClient.alias` + +```python +alias(previous_id: ID_TYPES, distinct_id: Optional[str], timestamp: Optional[Union[datetime, str]] = None, uuid: Optional[str] = None, disable_geoip: Optional[bool] = None) -> Optional[str] +``` + +### `AsyncClient.capture` + +```python +capture(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] +``` + +Queue an event without blocking for network delivery. + +### `AsyncClient.capture_immediate` + +```python +capture_immediate(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] +``` + +Capture one event and wait until its delivery attempt completes. + +### `AsyncClient.capture_exception` + +```python +capture_exception(exception: Optional[ExceptionArg] = None, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] +``` + +Capture an exception. This method never raises, including in debug mode. + +### `AsyncClient.emails` + +```python +emails: AsyncEmails +``` + +Send transactional email from PostHog email templates, and read the +status of each email sent with `get`. + +Needs `secret_key`, a project secret API key with the `hog_flow:write` +scope, and `posthog[async]`. Sends go to `app_host`, or to the PostHog +Cloud app host of `host`. `email_request_timeout_seconds` and +`email_request_max_retries` configure each send. A disabled or shut-down +client sends nothing. See `posthog.emails` for setup. + +**Examples:** + +```python +await client.emails.send( + to="user@example.com", + template_id="0190f6d2-7c1e-7b3a-9f5e-1c2d3e4f5a6b", + distinct_id="user_123", +) +``` + +### `AsyncClient.flush` + +```python +flush(timeout_seconds: Optional[float] = 10) -> None +``` + +### `AsyncClient.evaluate_flags` + +```python +evaluate_flags(distinct_id: Optional[ID_TYPES] = None, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, disable_geoip: Optional[bool] = None, flag_keys: Optional[list[str]] = None, device_id: Optional[str] = None) -> FeatureFlagEvaluations +``` + +Evaluate flags remotely without blocking the event loop. + +The returned snapshot has synchronous in-memory accessors. Pass it to +`capture(flags=...)` to attach the exact values used for branching. +This client does not evaluate flags locally, so the snapshot's +`unresolved_flags` is always empty. + +### `AsyncClient.group_identify` + +```python +group_identify(group_type: str, group_key: str, properties: Optional[Dict[str, Any]] = None, timestamp: Optional[Union[datetime, str]] = None, uuid: Optional[Union[str, UUID]] = None, disable_geoip: Optional[bool] = None, distinct_id: Optional[ID_TYPES] = None) -> Optional[str] +``` + +### `AsyncClient.set` + +```python +set(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str] +``` + +### `AsyncClient.set_once` + +```python +set_once(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str] +``` + +### `AsyncClient(...)` + +```python +__init__(project_api_key: str, host: Optional[str] = None, debug: bool = False, max_queue_size: int = 10000, send: bool = True, on_error: bool = None, flush_at: int = 100, flush_interval: float = 5.0, gzip: bool = False, max_retries: int = 3, timeout: float = 15, thread: int = 1, disabled: bool = False, disable_geoip: bool = True, is_server: bool = True, historical_migration: bool = False, super_properties: Optional[dict[str, Any]] = None, before_send: Optional[dict[str, Any]] = None, log_captured_exceptions: bool = False, project_root: Optional[str] = None, capture_exception_code_variables: bool = False, code_variables_mask_patterns: bool = None, code_variables_ignore_patterns: bool = None, code_variables_mask_url_credentials: bool = None, code_variables_detect_secrets: bool = None, in_app_modules: Optional[list[str]] = None, capture_mode: Optional[Union[CaptureMode, str]] = None, capture_compression: Optional[Union[CaptureCompression, str]] = None, capture_trace_context: bool = False, secret_key: Optional[str] = None, personal_api_key: Optional[str] = None, feature_flags_request_timeout_seconds: float = 3, feature_flags_request_max_retries: int = 1, app_host: Optional[str] = None, email_request_timeout_seconds: float = 15, email_request_max_retries: int = 2) -> None +``` + +### `AsyncClient.shutdown` + +```python +shutdown() -> None +``` + +### `AsyncClient.join` + +```python +join() -> None +``` + +### `AsyncClient.get_remote_config_payload` + +```python +get_remote_config_payload(key: str) -> Optional[Any] +``` + +Fetch and decrypt a remote-config payload without blocking the loop. + +## Module functions + +### `posthog.new_context` + +```python +new_context(fresh: bool = False, capture_exceptions: Optional[bool] = None, client: Optional[Client] = None) +``` + +Create a new context scope that will be active for the duration of the with block. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`fresh` | bool | Whether to start with a fresh context (default: False) | False +`capture_exceptions` | Optional[bool] | Whether to capture exceptions raised within the context. If omitted, defaults to the relevant client's exception autocapture setting. | None +`client` | Optional[Client] | Optional Posthog client instance to use for this context (default: None) | None + +**Examples:** + +```python +from posthog import new_context, tag, capture +with new_context(): + tag("request_id", "123") + capture("event_name", properties={"property": "value"}) +``` + +
+Category + +Contexts + +
+ +### `posthog.scoped` + +```python +scoped(fresh = False, capture_exceptions: Optional[bool] = None) +``` + +Decorator that creates a new context for the function. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`fresh` | | Whether to start with a fresh context (default: False) | False +`capture_exceptions` | Optional[bool] | Whether to capture and track exceptions with posthog error tracking. If omitted, defaults to the global exception autocapture setting. | None + +**Examples:** + +```python +from posthog import scoped, tag, capture +@scoped() +def process_payment(payment_id): + tag("payment_id", payment_id) + capture("payment_started") +``` + +
+Category + +Contexts + +
+ +### `posthog.set_context_session` + +```python +set_context_session(session_id: str) +``` + +Set the session ID for the current context. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`session_id` | str | The session ID to associate with the current context and its children | *required* + +**Examples:** + +```python +from posthog import set_context_session +set_context_session("session_123") +``` + +
+Category + +Contexts + +
+ +### `posthog.set_context_device_id` + +```python +set_context_device_id(device_id: str) +``` + +Set the device ID for the current context, associating all feature flag requests +in this or child contexts with the given device ID. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`device_id` | str | The device ID to associate with the current context and its children | *required* + +**Examples:** + +```python +from posthog import set_context_device_id +set_context_device_id("device_123") +``` + +
+Category + +Contexts + +
+ +### `posthog.identify_context` + +```python +identify_context(distinct_id: str) +``` + +Identify the current context with a distinct ID. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | str | The distinct ID to associate with the current context and its children | *required* + +**Examples:** + +```python +from posthog import identify_context +identify_context("user_123") +``` + +
+Category + +Identification + +
+ +### `posthog.set_capture_exception_code_variables_context` + +```python +set_capture_exception_code_variables_context(enabled: bool) +``` + +Override code-variable capture for exceptions in the current context. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`enabled` | bool | Whether exceptions captured in this context should include local variable values from stack frames. | *required* + +
+Category + +Contexts + +
+ +### `posthog.set_code_variables_mask_patterns_context` + +```python +set_code_variables_mask_patterns_context(mask_patterns: list[str]) +``` + +Override code-variable mask patterns for exceptions in the current context. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`mask_patterns` | list[str] | Variable-name patterns whose values should be replaced with `***` when code variables are captured. | *required* + +
+Category + +Contexts + +
+ +### `posthog.set_code_variables_ignore_patterns_context` + +```python +set_code_variables_ignore_patterns_context(ignore_patterns: list[str]) +``` + +Override code-variable ignore patterns for exceptions in the current context. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`ignore_patterns` | list[str] | Variable-name patterns that should be omitted entirely when code variables are captured. | *required* + +
+Category + +Contexts + +
+ +### `posthog.set_code_variables_mask_url_credentials_context` + +```python +set_code_variables_mask_url_credentials_context(enabled: bool) +``` + +Whether to scrub credentials embedded in URLs/DSNs (e.g. user:pass@host) from +captured code variables for the current context. + +### `posthog.set_code_variables_detect_secrets_context` + +```python +set_code_variables_detect_secrets_context(enabled: bool) +``` + +Whether to apply entropy-based secret detection as a last-resort redaction of +high-entropy values (API keys, tokens, strong passwords) in captured code +variables for the current context. + +
+Category + +Contexts + +
+ +### `posthog.tag` + +```python +tag(name: str, value: Any) +``` + +Add a tag to the current context. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`name` | str | The tag key | *required* +`value` | Any | The tag value | *required* + +**Examples:** + +```python +from posthog import tag +tag("user_id", "123") +``` + +
+Category + +Contexts + +
+ +### `posthog.get_tags` + +```python +get_tags() -> Dict[str, Any] +``` + +Get all tags from the current context. + +**Returns:** + +Type | Description +---- | ----------- +Dict[str, Any] | Dict of all tags in the current context + +
+Category + +Contexts + +
+ +### `posthog.capture` + +```python +capture(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] +``` + +Capture anything a user does within your system. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`event` | str | The event name to specify the event | *required* +`**kwargs` | Unpack[OptionalCaptureArgs] | Optional arguments including: distinct_id: Unique identifier for the user properties: Dict of event properties timestamp: When the event occurred. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. uuid: Unique identifier for this event. If omitted, one is generated and returned. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. groups: Dict of group types and IDs flags: A FeatureFlagEvaluations snapshot from evaluate_flags(). The exact values from the snapshot are attached with no extra /flags request. send_feature_flags: Deprecated. Prefer flags=... from evaluate_flags(). When truthy, evaluates flags during capture and attaches them to the event. disable_geoip: Whether to disable GeoIP lookup | {} + +
+Details + +Capture allows you to capture anything a user does within your system, which you can later use in PostHog to find patterns in usage, work out which features to improve or where people are giving up. A capture call requires an event name to specify the event. We recommend using [verb] [noun], like `movie played` or `movie updated` to easily identify what your events mean later on. Capture takes a number of optional arguments, which are defined by the `OptionalCaptureArgs` type. + +
+ +**Examples:** + +```python +# Context and capture usage +from posthog import new_context, identify_context, tag_context, capture +# Enter a new context (e.g. a request/response cycle, an instance of a background job, etc) +with new_context(): + # Associate this context with some user, by distinct_id + identify_context('some user') + + # Capture an event, associated with the context-level distinct ID ('some user') + capture('movie started') + + # Capture an event associated with some other user (overriding the context-level distinct ID) + capture('movie joined', distinct_id='some-other-user') + + # Capture an event with some properties + capture('movie played', properties={'movie_id': '123', 'category': 'romcom'}) + + # Capture an event with some properties + capture('purchase', properties={'product_id': '123', 'category': 'romcom'}) + # Capture an event with some associated group + capture('purchase', groups={'company': 'id:5'}) + + # Adding a tag to the current context will cause it to appear on all subsequent events + tag_context('some-tag', 'some-value') + + capture('another-event') # Will be captured with `'some-tag': 'some-value'` in the properties dict +``` +```python +# Set event properties +from posthog import capture +capture( + "user_signed_up", + distinct_id="distinct_id_of_the_user", + properties={ + "login_type": "email", + "is_free_trial": "true" + } +) +``` + +### `posthog.capture_ai` + +```python +capture_ai(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] +``` + +Capture an AI event on the dedicated AI capture endpoint. + +Beta: the signature is stable; operational limits (per-event size cap, +batching, endpoint) may change without notice. + +Takes the same arguments and returns the same value as `capture()`: the +event UUID, or None when the event was not admitted (disabled client, or +dropped by `before_send`). The event is delivered on an isolated queue +with its own consumer pool and a higher per-event size cap, posting to +the dedicated AI ingestion endpoint. The payload is sent as given — no +redaction or truncation is applied here. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`event` | str | The event name, normally one of the `$ai_*` event names. | *required* +`**kwargs` | Unpack[OptionalCaptureArgs] | Same optional arguments as `capture()`. | {} + +**Examples:** + +```python +from posthog import capture_ai + +uuid = capture_ai( + "$ai_generation", + distinct_id="user_123", + properties={"$ai_model": "gpt-5"}, +) +``` + +### `posthog.set` + +```python +set(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str] +``` + +Set properties on a user record. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`**kwargs` | Unpack[OptionalSetArgs] | Optional arguments including: distinct_id: Unique identifier for the user. Falls back to the context distinct ID; if none exists, this call does nothing. properties: Dict of person properties to set. timestamp: When the properties were set. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. uuid: Unique identifier for this operation. If omitted, one is generated and returned. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. disable_geoip: Whether to disable GeoIP lookup. | {} + +
+Details + +This will overwrite previous people property values. Generally operates similar to `capture`, with distinct_id being an optional argument, defaulting to the current context's distinct ID. If there is no context-level distinct ID, and no override distinct_id is passed, this function will do nothing. Context tags are folded into $set properties, so tagging the current context and then calling `set` will cause those tags to be set on the user (unlike capture, which causes them to just be set on the event). + +
+ +**Examples:** + +```python +# Set person properties +from posthog import set +set(distinct_id='distinct_id', properties={'name': 'Max Hedgehog'}) +``` + +### `posthog.set_once` + +```python +set_once(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str] +``` + +Set properties on a user record, only if they do not yet exist. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`**kwargs` | Unpack[OptionalSetArgs] | Optional arguments including: distinct_id: Unique identifier for the user. Falls back to the context distinct ID; if none exists, this call does nothing. properties: Dict of person properties to set only once. timestamp: When the properties were set. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. uuid: Unique identifier for this operation. If omitted, one is generated and returned. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. disable_geoip: Whether to disable GeoIP lookup. | {} + +
+Details + +This will not overwrite previous people property values, unlike `set`. Otherwise, operates in an identical manner to `set`. + +
+ +**Examples:** + +```python +# Set property once +from posthog import set_once +set_once(distinct_id='distinct_id', properties={'initial_url': '/blog'}) + +``` + +### `posthog.group_identify` + +```python +group_identify(group_type: str, group_key: str, properties: Optional[Dict[str, Any]] = None, timestamp: Optional[Union[datetime.datetime, str]] = None, uuid: Optional[str] = None, disable_geoip: Optional[bool] = None, distinct_id: Optional[ID_TYPES] = None) -> Optional[str] +``` + +Set properties on a group. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`group_type` | str | Type of your group. Required - the call is dropped with a warning if it is missing or empty. | *required* +`group_key` | str | Unique identifier of the group. Required - the call is dropped with a warning if it is missing or empty. | *required* +`properties` | Optional[Dict[str, Any]] | Properties to set on the group | None +`timestamp` | Optional[Union[datetime, str]] | Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | None +`uuid` | Optional[str] | Optional UUID for the event | None +`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup | None +`distinct_id` | Optional[ID_TYPES] | Optional distinct ID of the user performing the action | None + +**Examples:** + +```python +# Group identify +from posthog import group_identify +group_identify('company', 'company_id_in_your_db', { + 'name': 'Awesome Inc.', + 'employees': 11 +}) +``` + +### `posthog.alias` + +```python +alias(previous_id: ID_TYPES, distinct_id: str, timestamp: Optional[Union[datetime.datetime, str]] = None, uuid: Optional[str] = None, disable_geoip: Optional[bool] = None) -> Optional[str] +``` + +Associate user behaviour before and after they e.g. register, login, or perform some other identifying action. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`previous_id` | ID_TYPES | The unique ID of the user before | *required* +`distinct_id` | str | The current unique id | *required* +`timestamp` | Optional[Union[datetime, str]] | Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | None +`uuid` | Optional[str] | Optional UUID for the event | None +`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup | None + +
+Details + +To marry up whatever a user does before they sign up or log in with what they do after you need to make an alias call. This will allow you to answer questions like "Which marketing channels leads to users churning after a month?" or "What do users do on our website before signing up?". Particularly useful for associating user behaviour before and after they e.g. register, login, or perform some other identifying action. + +
+ +**Examples:** + +```python +# Alias user +from posthog import alias +alias(previous_id='distinct_id', distinct_id='alias_id') +``` + +### `posthog.capture_exception` + +```python +capture_exception(exception: Optional[ExceptionArg] = None, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] +``` + +Capture exceptions that happen in your code. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`exception` | Optional[ExceptionArg] | The exception to capture. If not provided, the current exception is captured via `sys.exc_info()` | None +`**kwargs` | Unpack[OptionalCaptureArgs] | Optional capture arguments including distinct_id, properties, timestamp, uuid, groups, flags, send_feature_flags, and disable_geoip. Overriding reserved exception properties through `properties` is deprecated and will stop working in the next major version. | {} + +
+Details + +Capture exception is idempotent - if it is called twice with the same exception instance, only a occurrence will be tracked in posthog. This is because, generally, contexts will cause exceptions to be captured automatically. However, to ensure you track an exception, if you catch and do not re-raise it, capturing it manually is recommended, unless you are certain it will have crossed a context boundary (e.g. by existing a `with posthog.new_context():` block already). If the passed exception was raised and caught, the captured stack trace will consist of every frame between where the exception was raised and the point at which it is captured (the "traceback"). If the passed exception was never raised, e.g. if you call `posthog.capture_exception(ValueError("Some Error"))`, the stack trace captured will be the full stack trace at the moment the exception was captured. Note that heavy use of contexts will lead to truncated stack traces, as the exception will be captured by the context entered most recently, which may not be the point you catch the exception for the final time in your code. It's recommended to use contexts sparingly, for this reason. `capture_exception` takes the same set of optional arguments as `capture`. + +
+ +**Examples:** + +```python +# Capture exception +from posthog import capture_exception +try: + risky_operation() +except Exception as e: + capture_exception(e) +``` + +### `posthog.feature_enabled` + +```python +feature_enabled(key: str, distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[bool] +``` + +Use feature flags to enable or disable features for users. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The feature flag key | *required* +`distinct_id` | ID_TYPES | The user's distinct ID | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | Groups mapping | None +`person_properties` | Optional[Dict[str, Any]] | Person properties | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties | None +`only_evaluate_locally` | bool | Whether to evaluate only locally | False +`send_feature_flag_events` | bool | Whether to send feature flag events | True +`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup | None +`device_id` | Optional[str] | Optional device ID override for experience-continuity flags | None + +
+Details + +You can call `posthog.load_feature_flags()` before to make sure you're not doing unexpected requests. + +
+ +**Examples:** + +```python +# Boolean feature flag +from posthog import feature_enabled, get_feature_flag_payload +is_my_flag_enabled = feature_enabled('flag-key', 'distinct_id_of_your_user') +if is_my_flag_enabled: + matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user') +``` + +### `posthog.get_feature_flag` + +```python +get_feature_flag(key: str, distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[FlagValue] +``` + +Get feature flag variant for users. Used with experiments. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The feature flag key | *required* +`distinct_id` | ID_TYPES | The user's distinct ID | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | Groups mapping from group type to group key | None +`person_properties` | Optional[Dict[str, Any]] | Person properties | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties in format { group_type_name: { group_properties } } | None +`only_evaluate_locally` | bool | Whether to evaluate only locally | False +`send_feature_flag_events` | bool | Whether to send feature flag events | True +`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup | None +`device_id` | Optional[str] | Optional device ID override for experience-continuity flags | None + +
+Details + +`groups` are a mapping from group type to group key. So, if you have a group type of "organization" and a group key of "5", you would pass groups={"organization": "5"}. `group_properties` take the format: { group_type_name: { group_properties } }. So, for example, if you have the group type "organization" and the group key "5", with the properties name, and employee count, you'll send these as: group_properties={"organization": {"name": "PostHog", "employees": 11}}. + +
+ +**Examples:** + +```python +# Multivariate feature flag +from posthog import get_feature_flag, get_feature_flag_payload +enabled_variant = get_feature_flag('flag-key', 'distinct_id_of_your_user') +if enabled_variant == 'variant-key': + matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user') +``` + +### `posthog.get_all_flags` + +```python +get_all_flags(distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None, flag_keys_to_evaluate: Optional[list[str]] = None) -> Optional[dict[str, FlagValue]] +``` + +Get all flags for a given user. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | ID_TYPES | The user's distinct ID | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | Groups mapping | None +`person_properties` | Optional[Dict[str, Any]] | Person properties | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties | None +`only_evaluate_locally` | bool | Whether to evaluate only locally | False +`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup | None +`device_id` | Optional[str] | Optional device ID override for experience-continuity flags | None +`flag_keys_to_evaluate` | Optional[list[str]] | Optional list of flag keys to evaluate (evaluates all if None) | None + +
+Details + +Flags are key-value pairs where the key is the flag key and the value is the flag variant, or True, or False. + +
+ +**Examples:** + +```python +# All flags for user +from posthog import get_all_flags +get_all_flags('distinct_id_of_your_user') +``` + +### `posthog.get_feature_flag_result` + +```python +get_feature_flag_result(key: str, distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[FeatureFlagResult] +``` + +Get a FeatureFlagResult object which contains the flag result and payload. + +This method evaluates a feature flag and returns a FeatureFlagResult object containing: +- enabled: Whether the flag is enabled +- variant: The variant value if the flag has variants +- payload: The payload associated with the flag (automatically deserialized from JSON) +- key: The flag key +- reason: Why the flag was enabled/disabled + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The feature flag key. | *required* +`distinct_id` | ID_TYPES | The user's distinct ID. | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | Mapping of group type to group key. | None +`person_properties` | Optional[Dict[str, Any]] | Person properties to use for evaluation. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties keyed by group type. | None +`only_evaluate_locally` | bool | Whether to evaluate only locally. | False +`send_feature_flag_events` | bool | Whether to send a $feature_flag_called event. | True +`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup. | None +`device_id` | Optional[str] | Optional device ID override for experience-continuity flags. | None + +Example: +```python +result = posthog.get_feature_flag_result('beta-feature', 'distinct_id') +if result and result.enabled: + # Use the variant and payload + print(f"Variant: {result.variant}") + print(f"Payload: {result.payload}") +``` + +### `posthog.get_feature_flag_payload` + +```python +get_feature_flag_payload(key: str, distinct_id: ID_TYPES, match_value: Optional[FlagValue] = None, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[object] +``` + +Get the payload associated with a feature flag value. + +Deprecated for new code. Prefer `evaluate_flags()` and +`flags.get_flag_payload(key)` so flag evaluation happens once per request. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The feature flag key. | *required* +`distinct_id` | ID_TYPES | The user's distinct ID. | *required* +`match_value` | Optional[FlagValue] | Optional flag value to use when selecting a payload. | None +`groups` | Optional[Mapping[str, Union[str, int]]] | Mapping of group type to group key. | None +`person_properties` | Optional[Dict[str, Any]] | Person properties to use for evaluation. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties keyed by group type. | None +`only_evaluate_locally` | bool | Whether to evaluate only locally. | False +`send_feature_flag_events` | bool | Whether to send a $feature_flag_called event. | True +`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup. | None +`device_id` | Optional[str] | Optional device ID override for experience-continuity flags. | None + +**Returns:** + +Type | Description +---- | ----------- +Optional[object] | The payload associated with the matched feature flag value, or None. +Optional[object] | This function returns the payload only, not the FeatureFlagResult wrapper +Optional[object] | used internally to compute it. + +### `posthog.get_remote_config_payload` + +```python +get_remote_config_payload(key: str) +``` + +Get the payload for a remote config feature flag. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The key of the feature flag | *required* + +**Returns:** + +Type | Description +---- | ----------- + | The payload associated with the feature flag. If payload is encrypted, the return value will be decrypted + +
+Note + +Requires secret_key to be set for authentication + +
+ +### `posthog.get_all_flags_and_payloads` + +```python +get_all_flags_and_payloads(distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None, flag_keys_to_evaluate: Optional[list[str]] = None) -> FlagsAndPayloads +``` + +Get all feature flag values and payloads for a user. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | ID_TYPES | The user's distinct ID. | *required* +`groups` | Optional[Mapping[str, Union[str, int]]] | Mapping of group type to group key. | None +`person_properties` | Optional[Dict[str, Any]] | Person properties to use for evaluation. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties keyed by group type. | None +`only_evaluate_locally` | bool | Whether to evaluate only locally. | False +`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup. | None +`device_id` | Optional[str] | Optional device ID override for experience-continuity flags. | None +`flag_keys_to_evaluate` | Optional[list[str]] | Optional list of flag keys to evaluate. Evaluates all flags when omitted. | None + +**Returns:** + +Type | Description +---- | ----------- +FlagsAndPayloads | A dict with `featureFlags` and `featureFlagPayloads` entries. + +### `posthog.evaluate_flags` + +```python +evaluate_flags(distinct_id: Optional[ID_TYPES] = None, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, flag_keys: Optional[list[str]] = None, device_id: Optional[str] = None) -> FeatureFlagEvaluations +``` + +Evaluate all feature flags for a user in a single call and return a +`FeatureFlagEvaluations` snapshot. Branch on `.is_enabled()` / +`.get_flag()` and pass the same snapshot to `capture()` via the +`flags` option so events carry the exact flag values the code branched on. + +Prefer this over repeated `get_feature_flag()` calls and over +`capture(send_feature_flags=True)` — it consolidates flag evaluation into +a single `/flags` request per incoming request. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | Optional[ID_TYPES] | The user's distinct ID. If `None`, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot. | None +`groups` | Optional[Mapping[str, Union[str, int]]] | Mapping of group type to group key. | None +`person_properties` | Optional[Dict[str, Any]] | Person properties to use for evaluation. | None +`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties keyed by group type. | None +`only_evaluate_locally` | bool | If `True`, never fall back to remote evaluation and omit flags that cannot be evaluated locally. The snapshot's `unresolved_flags` lists them with a reason. | False +`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup. | None +`flag_keys` | Optional[list[str]] | Optional list that scopes local evaluation, the underlying `/flags` request, and the returned snapshot. When omitted or `None`, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per `evaluate_flags` call unless `only_evaluate_locally` is `True`. If the server also does not know the key, it is omitted from the snapshot. | None +`device_id` | Optional[str] | Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes. | None + +**Examples:** + +```python +from posthog import evaluate_flags, capture +flags = evaluate_flags("user_123", person_properties={"plan": "enterprise"}) +if flags.is_enabled("new-dashboard"): + render_new_dashboard() +capture("page_viewed", distinct_id="user_123", flags=flags) +``` + +### `posthog.feature_flag_definitions` + +```python +feature_flag_definitions() +``` + +Returns loaded feature flags. + +
+Details + +Returns loaded feature flags, if any. Helpful for debugging what flag information you have loaded. + +
+ +**Examples:** + +```python +from posthog import feature_flag_definitions +definitions = feature_flag_definitions() +``` + +### `posthog.get_feature_flag_evaluation_runtime` + +```python +get_feature_flag_evaluation_runtime(key: str) -> Optional[FeatureFlagEvaluationRuntime] +``` + +Return where a locally loaded feature flag is meant to be evaluated. + +
+Details + +Reads the `evaluation_runtime` each flag definition carries, so no extra +request is made. Returns `None` when local evaluation has not loaded a +definition for this key. A definition that carries no runtime reports +`FeatureFlagEvaluationRuntime.ALL`, the default PostHog applies. + +
+ +**Examples:** + +```python +from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_evaluation_runtime +runtime = get_feature_flag_evaluation_runtime("my-flag") +``` + +
+Category + +Feature flags + +
+ +### `posthog.get_feature_flag_keys_by_evaluation_runtime` + +```python +get_feature_flag_keys_by_evaluation_runtime(evaluation_runtime: Union[FeatureFlagEvaluationRuntime, str]) -> list[str] +``` + +Return the keys of locally loaded flags that a runtime can evaluate. + +
+Details + +A flag set to `FeatureFlagEvaluationRuntime.ALL` suits either runtime, so +it is returned for `CLIENT` and for `SERVER`. Use this to decide which +flags to hand to a browser when a backend serves flags to its own +frontend. + +
+ +**Examples:** + +```python +from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_keys_by_evaluation_runtime +client_keys = get_feature_flag_keys_by_evaluation_runtime(FeatureFlagEvaluationRuntime.CLIENT) +``` + +
+Category + +Feature flags + +
+ +### `posthog.load_feature_flags` + +```python +load_feature_flags() +``` + +Load feature flag definitions from PostHog. + +**Examples:** + +```python +from posthog import load_feature_flags +load_feature_flags() +``` + +### `posthog.flush` + +```python +flush(timeout_seconds: Optional[float] = 10) -> None +``` + +Attempt immediate delivery of queued events and ended spans, keeping the global client reusable. + +Call `flush()` before a short-lived request or serverless invocation returns +when using the shared module-level client. Use `shutdown()` only for final +process cleanup or a client that will not be reused. Flush does not stop the +background workers or feature flag poller, and does not end open spans. + +Returning does not guarantee server receipt: pending work can remain after a +timeout or span export failure, and failed events may be dropped by consumers. +Calls made directly from SDK callbacks such as `on_error` are deferred +rather than blocking the worker that invoked the callback. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`timeout_seconds` | Optional[float] | Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass `None` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently, waiting up to this many seconds for an in-flight flush before starting a fresh drain budget. A lock wait timeout leaves the in-flight flush to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. The last span request is not cut short and is bounded by the client's `timeout` in seconds, so this is not a strict wall-clock limit for the whole call. With `None`, a retriable span failure is left for a later flush rather than retried within this call. | 10 + +**Examples:** + +```python +import posthog +posthog.capture('event_name', distinct_id='user_id') +# Before a serverless invocation returns; keep the global client open. +posthog.flush(timeout_seconds=10) +``` + +### `posthog.join` + +```python +join() -> None +``` + +Attempt to process queued events and stop the client's background workers. Use `shutdown()` directly in most cases. + +Failed or undrainable events may be dropped and reported through logging or +`on_error`; returning does not guarantee server receipt. Lifecycle cleanup +is attempted once, and cleanup failures are logged without retry. + +**Examples:** + +```python +from posthog import join +join() +``` + +### `posthog.start_span` + +```python +start_span(name: str, *, kind: Optional[str] = None, attributes: Optional[Mapping[str, Any]] = None, parent: Union[Span, str, None] = None, tracestate: Optional[str] = None, start_time: Union[datetime.datetime, float, None] = None) -> Span +``` + +Start a span for distributed tracing. Alpha. + +Returns a span handle. Use it as a context manager to make it the active +span for the block and end it on exit (recording a raised exception on the +way out); or call `end()` yourself for a span that cannot wrap a block. +Spans started inside the block nest under it automatically. Always returns +a usable handle, even when tracing is off, so calling code never branches. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`name` | str | A low-cardinality operation name, e.g. `GET /users/:id`. Variable values belong in attributes, not the name. | *required* +`kind` | Optional[str] | `internal` (default), `server`, `client`, `producer` or `consumer`. | None +`attributes` | Optional[Mapping[str, Any]] | Initial attributes. | None +`parent` | Union[Span, str, None] | A span handle, or an inbound W3C `traceparent` header value to continue a remote trace. Defaults to the active span. | None +`tracestate` | Optional[str] | The inbound `tracestate` header accompanying a `traceparent` string `parent`; preserved and propagated. | None +`start_time` | Union[datetime, float, None] | A `datetime` or epoch seconds, to backdate the span. | None + +**Examples:** + +```python +import posthog +posthog.traces = {"service_name": "checkout-api"} + +with posthog.start_span("POST /checkout", parent=request.headers.get("traceparent")) as span: + span.set_attribute("plan", user.plan) + with posthog.start_span("db.query", kind="client"): + ... + outgoing_headers = {"traceparent": span.traceparent()} +``` + +### `posthog.get_active_span` + +```python +get_active_span() -> Optional[Span] +``` + +The span that is active in the current context, or `None`. Alpha. + +Only entering a span (`with posthog.start_span(...) as span:`) makes it +active; a span started manually is not. Use it to propagate the trace to +the next service: `span.traceparent()` is the header value. + +**Examples:** + +```python +span = posthog.get_active_span() +if span is not None: + headers["traceparent"] = span.traceparent() +``` + +### `posthog.shutdown` + +```python +shutdown() -> None +``` + +Attempt final delivery and permanently shut down the global client. + +Call this for final process cleanup, not at the end of each request or +serverless invocation that shares the module-level client. Use `flush()` +before returning from those invocations instead. Shutdown closes capture +lanes, stops background workers and the feature flag poller, and tears down +integrations. The global client is not recreated automatically after shutdown. + +This normally blocks until queued events have been attempted and cleanup +finishes. Failed or undrainable events may be dropped and reported through +logging or `on_error`; returning does not guarantee server receipt. Event +draining has no timeout. Queued spans get a final flush with up to 30 seconds +waiting for an in-flight flush, then a fresh 30-second drain budget; the last +HTTP request is not cut short. Unsent queued spans and still-open spans are +discarded with a warning. +Lifecycle cleanup is attempted once, and cleanup failures are logged without +retry. Calls made directly from SDK callbacks such as `on_error` are deferred +to avoid deadlocking the worker. If blocking completion is required, signal an application-owned +thread, return from the callback, and call `shutdown()` from that thread. +Do not wait inside a callback for another thread or task calling a lifecycle +method. + +**Examples:** + +```python +from posthog import shutdown +shutdown() +``` + +### `posthog.setup` + +```python +setup() -> Client +``` + +Create or return the global PostHog client configured by module settings. + +Most applications should either instantiate `Posthog` directly or set +`posthog.api_key`/other module settings before calling top-level helpers. +`setup()` is called automatically by global APIs such as `capture()`. + +**Returns:** + +Type | Description +---- | ----------- +Client | The global `Client` instance. If both `api_key` and +Client | `project_api_key` are missing or blank, the client is disabled and +Client | module-level calls become no-ops. + +## Types + +### `OptionalCaptureArgs` + +Bases: TypedDict + +Optional arguments for the capture method. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`distinct_id` | | Unique identifier for the person associated with this event. If not set, the context distinct_id is used, if available, otherwise a UUID is generated, and the event is marked as personless. Setting context-level distinct_id's is recommended. | +`properties` | | Dictionary of properties to track with the event | +`timestamp` | | When the event occurred (defaults to current time). UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | +`uuid` | | Unique identifier for this specific event. If not provided, one is generated. The event UUID is returned, so you can correlate it with actions in your app (like showing users an error ID if you capture an exception). If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | +`groups` | | Group identifiers to associate with this event (format: {group_type: group_key}) | +`flags` | | A `FeatureFlagEvaluations` snapshot from `evaluate_flags()`. The exact flag values from the snapshot are attached to the event with no additional network call — prefer this over `send_feature_flags`. | +`send_feature_flags` | | Deprecated — prefer `flags` with a `FeatureFlagEvaluations` snapshot. Whether to include currently active feature flags in the event properties. Can be a boolean or a SendFeatureFlagsOptions object. Defaults to False. Fires a hidden `/flags` request on capture and may return different values than the ones the code branched on. | +`disable_geoip` | | Whether to disable GeoIP lookup for this event. Defaults to False. | + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`disable_geoip` | NotRequired[Optional[bool]] | +`distinct_id` | NotRequired[Optional[ID_TYPES]] | +`flags` | NotRequired[Optional[FeatureFlagEvaluations]] | +`groups` | NotRequired[Optional[Dict[str, str]]] | +`properties` | NotRequired[Optional[Dict[str, Any]]] | +`send_feature_flags` | NotRequired[Optional[Union[bool, SendFeatureFlagsOptions]]] | +`timestamp` | NotRequired[Optional[Union[datetime, str]]] | +`uuid` | NotRequired[Optional[Union[str, UUID]]] | + +#### `posthog.args.OptionalCaptureArgs.disable_geoip` + +```python +disable_geoip: NotRequired[Optional[bool]] +``` + +#### `posthog.args.OptionalCaptureArgs.distinct_id` + +```python +distinct_id: NotRequired[Optional[ID_TYPES]] +``` + +#### `posthog.args.OptionalCaptureArgs.flags` + +```python +flags: NotRequired[Optional[FeatureFlagEvaluations]] +``` + +#### `posthog.args.OptionalCaptureArgs.groups` + +```python +groups: NotRequired[Optional[Dict[str, str]]] +``` + +#### `posthog.args.OptionalCaptureArgs.properties` + +```python +properties: NotRequired[Optional[Dict[str, Any]]] +``` + +#### `posthog.args.OptionalCaptureArgs.send_feature_flags` + +```python +send_feature_flags: NotRequired[Optional[Union[bool, SendFeatureFlagsOptions]]] +``` + +#### `posthog.args.OptionalCaptureArgs.timestamp` + +```python +timestamp: NotRequired[Optional[Union[datetime, str]]] +``` + +#### `posthog.args.OptionalCaptureArgs.uuid` + +```python +uuid: NotRequired[Optional[Union[str, UUID]]] +``` + +### `FeatureFlagEvaluations` + +```python +FeatureFlagEvaluations(host: _FeatureFlagEvaluationsHost, distinct_id: str, flags: Dict[str, _EvaluatedFlagRecord], groups: Optional[Mapping[str, Union[str, int]]] = None, disable_geoip: Optional[bool] = None, request_id: Optional[str] = None, evaluated_at: Optional[int] = None, errors_while_computing: bool = False, quota_limited: bool = False, minimal_flag_called_events: bool = False, accessed: Optional[Set[str]] = None, unresolved_flags: Optional[Mapping[str, UnresolvedFlagReason]] = None) -> None +``` + +A point-in-time snapshot of feature flag evaluations for a single distinct_id. + +Returned by `Client.evaluate_flags` — branch on `is_enabled` / +`get_flag` and pass the same object to `Client.capture` via the +`flags` option so the captured event carries the exact flag values the code +branched on. + +Example:: + + flags = posthog.evaluate_flags(distinct_id, person_properties={"plan": "enterprise"}) + if flags.is_enabled("new-dashboard"): + render_new_dashboard() + posthog.capture("page_viewed", distinct_id=distinct_id, flags=flags) + +To narrow the set of flags that get attached to a captured event, use the in-memory +helpers `only` and `only_accessed`. To narrow the set of flags requested +from the server in the first place, pass `flag_keys` to `Client.evaluate_flags`. + +**Functions:** + +Name | Description +---- | ----------- +`get_flag` | Return the flag value. Fires `$feature_flag_called` on first access. +`get_flag_payload` | Return the payload associated with a flag. +`is_enabled` | Return whether the flag is enabled. Fires `$feature_flag_called` on the +`only` | Return a filtered copy containing only flags with the given keys. Keys that +`only_accessed` | Return a filtered copy containing only flags accessed via `is_enabled` + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`keys` | List[str] | Return the flag keys that are part of this evaluation. +`unresolved_flags` | Mapping[str, UnresolvedFlagReason] | Return a read-only mapping of flag key to the reason local evaluation + +Internal — instances are created by the SDK via `Client.evaluate_flags()`. + +#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.get_flag` + +```python +get_flag(key: str, *, send_feature_flag_events: bool = True) -> Optional[FlagValue] +``` + +Return the flag value. Fires `$feature_flag_called` on first access. + +Returns the variant string for multivariate flags, `True` for enabled flags +without a variant, `False` for disabled flags, and `None` for flags that +were not returned by the evaluation. + +Pass `send_feature_flag_events=False` to skip the event for this read, as +with `is_enabled`. + +#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.get_flag_payload` + +```python +get_flag_payload(key: str) -> Optional[Any] +``` + +Return the payload associated with a flag. + +Does not count as an access for `only_accessed` and does not fire any event. + +#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.is_enabled` + +```python +is_enabled(key: str, default_value: bool = False, *, send_feature_flag_events: bool = True) -> bool +``` + +Return whether the flag is enabled. Fires `$feature_flag_called` on the +first access per (distinct_id, flag, value) tuple, deduped via the SDK's cache. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The feature flag key. | *required* +`default_value` | bool | Returned when the flag has no value in this evaluation — the key was not part of the evaluated set, or the evaluation itself came back empty (failed `/flags` request, quota limit, or no resolvable `distinct_id`). Defaults to `False`. | False +`send_feature_flag_events` | bool | Whether this read sends `$feature_flag_called`. When `False`, no event is sent and the dedupe cache is left alone, but the key still counts as accessed for `only_accessed`. | True + +A flag that has a value always wins over `default_value`, including a +disabled flag, which returns `False` even when `default_value=True`. + +#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.keys` + +```python +keys: List[str] +``` + +Return the flag keys that are part of this evaluation. + +#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.only` + +```python +only(keys: List[str]) -> FeatureFlagEvaluations +``` + +Return a filtered copy containing only flags with the given keys. Keys that +are not present in the evaluation are dropped and logged as a warning. + +#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.only_accessed` + +```python +only_accessed() -> FeatureFlagEvaluations +``` + +Return a filtered copy containing only flags accessed via `is_enabled` +or `get_flag` before this call. + +Order-dependent: if nothing has been accessed yet, the returned snapshot is +empty. The method honors its name — pre-access if you want a populated result. + +#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.unresolved_flags` + +```python +unresolved_flags: Mapping[str, UnresolvedFlagReason] +``` + +Return a read-only mapping of flag key to the reason local evaluation +left it without a value. + +A flag is listed when it has a loaded local definition, was in the requested +scope, was inconclusive locally, and no remote value filled the gap. Listed +flags are not part of `keys`, and reading one reports the +`local_evaluation_inconclusive` error instead of `flag_missing`. Reading +this mapping does not count as an access and does not fire any event. +Snapshots returned by `only` and `only_accessed`, and by +`AsyncClient.evaluate_flags()`, which evaluates only remotely, carry no +entries. + +### `FeatureFlagResult` + +```python +FeatureFlagResult(key: str, enabled: bool, variant: Optional[str], payload: Optional[Any], reason: Optional[str]) -> None +``` + +The result of calling a feature flag which includes the flag result, variant, and payload. + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`key` | str | The unique identifier of the feature flag. +`enabled` | bool | Whether the feature flag is enabled for the current context. +`variant` | Optional[str] | The variant value if the flag is enabled and has variants, None otherwise. +`payload` | Optional[Any] | Additional data associated with the feature flag, if any. +`reason` | Optional[str] | A description of why the flag was enabled or disabled, if available. + +**Functions:** + +Name | Description +---- | ----------- +`from_flag_details` | Create a FeatureFlagResult from a FeatureFlag object. +`from_value_and_payload` | Creates a FeatureFlagResult from a flag value and payload. +`get_value` | Returns the value of the flag. This is the variant if it exists, otherwise the enabled value. + +#### `posthog.types.FeatureFlagResult.enabled` + +```python +enabled: bool +``` + +#### `posthog.types.FeatureFlagResult.from_flag_details` + +```python +from_flag_details(details: Union[FeatureFlag, None], override_match_value: Optional[FlagValue] = None) -> FeatureFlagResult | None +``` + +Create a FeatureFlagResult from a FeatureFlag object. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`details` | Union[FeatureFlag, None] | The FeatureFlag object to convert. | *required* +`override_match_value` | Optional[FlagValue] | If provided, this value will be used to populate the enabled and variant fields instead of the values from the FeatureFlag. | None + +**Returns:** + +Type | Description +---- | ----------- +FeatureFlagResult \| None | FeatureFlagResult | None: A new FeatureFlagResult instance, or None if details is None. + +#### `posthog.types.FeatureFlagResult.from_value_and_payload` + +```python +from_value_and_payload(key: str, value: Union[FlagValue, None], payload: Any) -> Union[FeatureFlagResult, None] +``` + +Creates a FeatureFlagResult from a flag value and payload. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`key` | str | The unique identifier of the feature flag. | *required* +`value` | Union[FlagValue, None] | The value of the flag (string variant or boolean). | *required* +`payload` | Any | Additional data associated with the feature flag. | *required* + +**Returns:** + +Type | Description +---- | ----------- +Union[FeatureFlagResult, None] | Union[FeatureFlagResult, None]: A new FeatureFlagResult instance, or None if value is None. + +#### `posthog.types.FeatureFlagResult.get_value` + +```python +get_value() -> FlagValue +``` + +Returns the value of the flag. This is the variant if it exists, otherwise the enabled value. +This is the value we report as `$feature_flag_response` in the `$feature_flag_called` event. + +**Returns:** + +Name | Type | Description +---- | ---- | ----------- +`FlagValue` | FlagValue | Either a string variant or boolean value representing the flag's state. + +#### `posthog.types.FeatureFlagResult.key` + +```python +key: str +``` + +#### `posthog.types.FeatureFlagResult.payload` + +```python +payload: Optional[Any] +``` + +#### `posthog.types.FeatureFlagResult.reason` + +```python +reason: Optional[str] +``` + +#### `posthog.types.FeatureFlagResult.variant` + +```python +variant: Optional[str] +``` + +### `FeatureFlag` + +```python +FeatureFlag(key: str, enabled: bool, variant: Optional[str], reason: Optional[FlagReason], metadata: Union[FlagMetadata, LegacyFlagMetadata]) -> None +``` + +Detailed feature flag evaluation returned by the flags API. + +`enabled`, `variant` and `metadata.payload` are the value rendered the way +responses before `/flags?v=4` carried it, which the legacy getters return. + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`key` | str | Feature flag key. +`enabled` | bool | Whether the flag is enabled for the evaluated user or group. +`variant` | Optional[str] | Variant key for multivariate flags, otherwise `None`. +`reason` | Optional[FlagReason] | Optional reason metadata explaining the result. +`metadata` | Union[FlagMetadata, LegacyFlagMetadata] | Payload and other metadata returned by the API. + +**Functions:** + +Name | Description +---- | ----------- +`from_json` | Parse one flag record of a `/flags` response. +`from_value_and_payload` | Build a flag from its value and payload. A string value is a variant, so the flag is enabled. +`get_value` | Return the variant for a multivariate flag, otherwise whether the flag is enabled. + +#### `posthog.types.FeatureFlag.enabled` + +```python +enabled: bool +``` + +#### `posthog.types.FeatureFlag.from_json` + +```python +from_json(resp: Any) -> FeatureFlag +``` + +Parse one flag record of a `/flags` response. + +A record with a `value` member is a `/flags?v=4` record. A record +without one comes from an older server: its value is `variant`, or +`enabled` when there is no variant, and its config version is `1`. + +#### `posthog.types.FeatureFlag.from_value_and_payload` + +```python +from_value_and_payload(key: str, value: FlagValue, payload: Any) -> FeatureFlag +``` + +Build a flag from its value and payload. A string value is a variant, so the flag is enabled. + +#### `posthog.types.FeatureFlag.get_value` + +```python +get_value() -> FlagValue +``` + +Return the variant for a multivariate flag, otherwise whether the flag is enabled. + +#### `posthog.types.FeatureFlag.key` + +```python +key: str +``` + +#### `posthog.types.FeatureFlag.metadata` + +```python +metadata: Union[FlagMetadata, LegacyFlagMetadata] +``` + +#### `posthog.types.FeatureFlag.reason` + +```python +reason: Optional[FlagReason] +``` + +#### `posthog.types.FeatureFlag.variant` + +```python +variant: Optional[str] +``` + +### `FlagsAndPayloads` + +Bases: TypedDict + +Feature flag values and payloads keyed by feature flag key. + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`featureFlags` | Optional[dict[str, FlagValue]] | Each flag's value, keyed by flag key. +`featureFlagPayloads` | Optional[dict[str, Any]] | Each flag's payload, keyed by flag key. + +#### `posthog.types.FlagsAndPayloads.featureFlagPayloads` + +```python +featureFlagPayloads: Optional[dict[str, Any]] +``` + +#### `posthog.types.FlagsAndPayloads.featureFlags` + +```python +featureFlags: Optional[dict[str, FlagValue]] +``` + +### `UnresolvedFlagReason` + +Bases: str, Enum + +Why local evaluation left a flag without a value. + +Reported by `FeatureFlagEvaluations.unresolved_flags`. Inheriting from +`str` keeps the members directly comparable to their string values. When more +than one cause applies, the first reason in the order below is reported. + +
+Values + +EXPERIENCE_CONTINUITY: The flag has experience continuity enabled, which + local evaluation never resolves. Change the flag to fix it. +UNSUPPORTED_DEFINITION: The flag uses something the local evaluator does not + support, such as a static cohort, an unknown operator, a malformed value, + a dependency with no loaded definition or a circular dependency. Change + the flag or upgrade the SDK to fix it. +UNRESOLVED_DEPENDENCY: A flag this flag depends on has a loaded definition + but was unresolved. +MISSING_CONTEXT: The call did not pass a property, group property or device + id the flag needs, or passed it in an unusable form. Pass it to fix it. + +
+ +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`EXPERIENCE_CONTINUITY` | | +`MISSING_CONTEXT` | | +`UNRESOLVED_DEPENDENCY` | | +`UNSUPPORTED_DEFINITION` | | + +#### `posthog.types.UnresolvedFlagReason.EXPERIENCE_CONTINUITY` + +```python +EXPERIENCE_CONTINUITY = 'experience_continuity' +``` + +#### `posthog.types.UnresolvedFlagReason.MISSING_CONTEXT` + +```python +MISSING_CONTEXT = 'missing_context' +``` + +#### `posthog.types.UnresolvedFlagReason.UNRESOLVED_DEPENDENCY` + +```python +UNRESOLVED_DEPENDENCY = 'unresolved_dependency' +``` + +#### `posthog.types.UnresolvedFlagReason.UNSUPPORTED_DEFINITION` + +```python +UNSUPPORTED_DEFINITION = 'unsupported_definition' +``` + +### `InconclusiveMatchError` + +```python +InconclusiveMatchError(*args, reason: Optional[UnresolvedFlagReason] = None) +``` + +Bases: Exception + +Raised when local evaluation can't determine a flag's value. `reason` says +why, as reported by `FeatureFlagEvaluations.unresolved_flags`. + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`reason` | UnresolvedFlagReason | Why local evaluation couldn't resolve the flag. + +#### `posthog.feature_flags.InconclusiveMatchError.reason` + +```python +reason: UnresolvedFlagReason = UnresolvedFlagReason.UNSUPPORTED_DEFINITION +``` + +### `RequiresServerEvaluation` + +```python +RequiresServerEvaluation(*args, reason: Optional[UnresolvedFlagReason] = None) +``` + +Bases: Exception + +Raised when feature flag evaluation requires server-side data that is not +available locally (e.g., static cohorts). + +This error should propagate immediately to trigger API fallback, unlike +InconclusiveMatchError which allows trying other conditions. + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`reason` | UnresolvedFlagReason | Why the flag needs server-side evaluation. + +#### `posthog.feature_flags.RequiresServerEvaluation.reason` + +```python +reason: UnresolvedFlagReason = UnresolvedFlagReason.UNSUPPORTED_DEFINITION +``` + +### `Span` + +A handle to a span. + +Every method is safe to call on any handle, including after `end()` and +on the inert handles returned when tracing is off, so calling code never +branches on whether tracing is running. Entering a handle (`with span:`) +makes it the active span for the block and ends it on exit, recording an +`Exception` raised inside it. A `BaseException` that is not an +`Exception` (`GeneratorExit`, `CancelledError`, `KeyboardInterrupt`) +still ends the span but is not recorded as a failure. + +**Functions:** + +Name | Description +---- | ----------- +`add_event` | Add a timestamped event to the span. Ignored after `end()`. +`end` | End the span and queue it for export. Idempotent. +`record_exception` | Record an exception as an `exception` event and mark the span `error`. +`set_attribute` | Set one attribute on the span; last write wins. Ignored after `end()`. +`set_attributes` | Set several attributes at once; last write wins per key. Ignored after `end()`. +`set_status` | Set the span status to `"ok"` or `"error"`. Ignored after `end()`. +`traceparent` | The W3C `traceparent` header value to propagate, or `None`. +`tracestate` | The W3C `tracestate` header value to propagate, or `None`. +`update_name` | Rename the span, for a name only known after it started. Ignored after `end()`. + +#### `posthog.tracing.span.Span.add_event` + +```python +add_event(name: str, attributes: Optional[Mapping[str, Any]] = None, timestamp: Union[datetime, float, None] = None) -> Span +``` + +Add a timestamped event to the span. Ignored after `end()`. + +`timestamp` defaults to now; it accepts a `datetime`, or seconds +since the epoch as a `float` or `int`. Returns the span, so calls +chain. + +**Examples:** + +```python +span.add_event("cache.miss", {"key": "user:42"}) +``` + +#### `posthog.tracing.span.Span.end` + +```python +end(end_time: Union[datetime, float, None] = None) -> None +``` + +End the span and queue it for export. Idempotent. + +`end_time` defaults to now; it accepts a `datetime`, or seconds +since the epoch as a `float` or `int`, and is never earlier than +the start. Later calls, and later mutations, no-op. `with span:` +ends the span on exit, so call this only for spans started manually. + +**Examples:** + +```python +span = posthog.start_span("job") +try: + run_job() +finally: + span.end() +``` + +#### `posthog.tracing.span.Span.record_exception` + +```python +record_exception(exception: BaseException) -> Span +``` + +Record an exception as an `exception` event and mark the span `error`. + +Ignored after `end()`. The event carries `exception.type`, +`exception.message` and, for a raised exception, +`exception.stacktrace` (its last `max_attribute_value_length` +characters, so a chained traceback keeps the outermost exception and +may lose its cause). Returns the span, so calls chain. Inside +`with span:` a raised `Exception` is recorded automatically, so +this is for exceptions that are caught and handled. + +**Examples:** + +```python +try: + charge(card) +except PaymentError as e: + span.record_exception(e) +``` + +#### `posthog.tracing.span.Span.set_attribute` + +```python +set_attribute(key: str, value: Any) -> Span +``` + +Set one attribute on the span; last write wins. Ignored after `end()`. + +Returns the span, so calls chain. Prefer primitive values: strings, +booleans, integers and floats keep their type. Lists and mappings are +sent as OTLP arrays and maps, but PostHog stores them as serialized +strings. `None` removes the key; anything else is stringified. + +**Examples:** + +```python +span.set_attribute("http.status_code", 200).set_attribute("cache.hit", True) +``` + +#### `posthog.tracing.span.Span.set_attributes` + +```python +set_attributes(attributes: Mapping[str, Any]) -> Span +``` + +Set several attributes at once; last write wins per key. Ignored after `end()`. + +Returns the span, so calls chain. Same encoding as `set_attribute()`. + +**Examples:** + +```python +span.set_attributes({"db.system": "postgresql", "db.rows": 12}) +``` + +#### `posthog.tracing.span.Span.set_status` + +```python +set_status(code: str, message: Optional[str] = None) -> Span +``` + +Set the span status to `"ok"` or `"error"`. Ignored after `end()`. + +Unset by default. Any other `code` is ignored. `ok` is final for the +scoped form: an `Exception` raised inside `with span:` does not override +it. Returns the span, so calls chain. + +**Examples:** + +```python +span.set_status("error", "upstream timed out") +``` + +#### `posthog.tracing.span.Span.traceparent` + +```python +traceparent() -> Optional[str] +``` + +The W3C `traceparent` header value to propagate, or `None`. + +#### `posthog.tracing.span.Span.tracestate` + +```python +tracestate() -> Optional[str] +``` + +The W3C `tracestate` header value to propagate, or `None`. + +#### `posthog.tracing.span.Span.update_name` + +```python +update_name(name: str) -> Span +``` + +Rename the span, for a name only known after it started. Ignored after `end()`. + +Returns the span, so calls chain. + +**Examples:** + +```python +span = posthog.start_span("http.request") +span.update_name(f"{request.method} {route.pattern}") +``` + +### `Emails` + +```python +Emails(secret_key: Optional[str], *, host: Optional[str] = None, timeout: float = 15, max_retries: int = 2, disabled: bool = False, session: Optional[requests.Session] = None) -> None +``` + +Bases: _EmailsBase + +Sends transactional email from PostHog email templates. + +`host` is your PostHog app host. PostHog Cloud ingestion hosts map to the +app host, so `us.i.posthog.com` sends to `us.posthog.com`. Any other +host is used as given. + +Each send retries network errors, rate limits and an unavailable service up +to `max_retries` times, with the same idempotency key. Set +`max_retries=0` when a job runner already retries the send. `timeout` +is in seconds, per request. Pass `session` to send through your own +`requests.Session`, for example a test double. + +**Functions:** + +Name | Description +---- | ----------- +`close` | Close the HTTP connections this client opened. A `session` you passed in stays open. +`get` | Read what happened to an email that `send` queued. +`send` | Queue one email from a transactional email template. + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`disabled` | | +`host` | | +`max_retries` | | +`secret_key` | | +`timeout` | | + +#### `posthog.emails.Emails.close` + +```python +close() -> None +``` + +Close the HTTP connections this client opened. A `session` you passed in stays open. + +#### `posthog.emails.Emails.disabled` + +```python +disabled = disabled +``` + +#### `posthog.emails.Emails.get` + +```python +get(message_id: Union[str, UUID]) -> EmailStatus +``` + +Read what happened to an email that `send` queued. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`message_id` | Union[str, UUID] | The `id` of the `SendEmailResult` that `send` returned. | *required* + +**Returns:** + +Type | Description +---- | ----------- +EmailStatus | The email's latest status, for 30 days after the send. + +**Raises:** + +Type | Description +---- | ----------- +EmailError | PostHog did not answer with a status. `code` is `not_found` for an unknown ID, an email older than 30 days, or for a few seconds after the send. +ValueError | `message_id` is not a message ID. + +**Examples:** + +```python +result = emails.send(to="user@example.com", template_id="...") +status = emails.get(result.id) +if status.status == "bounced": + print(status.reason) +``` + +#### `posthog.emails.Emails.host` + +```python +host = determine_app_host(host) +``` + +#### `posthog.emails.Emails.max_retries` + +```python +max_retries = max_retries +``` + +#### `posthog.emails.Emails.secret_key` + +```python +secret_key = secret_key.strip() if isinstance(secret_key, str) else None +``` + +#### `posthog.emails.Emails.send` + +```python +send(*, to: str, template_id: Union[str, UUID], template_data: Optional[Mapping[str, Any]] = None, distinct_id: Optional[str] = None, idempotency_key: Optional[str] = None, max_retries: Optional[int] = None) -> SendEmailResult +``` + +Queue one email from a transactional email template. + +**Parameters:** + +Name | Type | Description | Default +---- | ---- | ----------- | ------- +`to` | str | One recipient email address. | *required* +`template_id` | Union[str, UUID] | The UUID of a template made transactional in PostHog. | *required* +`template_data` | Optional[Mapping[str, Any]] | Values for the template's `{{ variables.* }}` placeholders. Values are encoded the way capture encodes properties. | None +`distinct_id` | Optional[str] | The recipient's distinct ID in your product. The email's events go to this person. Without it, they go to a person whose distinct ID is the email address. | None +`idempotency_key` | Optional[str] | 1 to 255 visible ASCII characters. A send with a key that PostHog has already accepted queues nothing and returns the first send's result. Without it, the client makes a key that covers its own retries. Pass a key derived from what the email is for, such as `welcome:user_123`, to cover retries outside this call, too. | None +`max_retries` | Optional[int] | Overrides the client's maximum number of retries for this send. `0` turns retries off. | None + +**Returns:** + +Type | Description +---- | ----------- +SendEmailResult | The queued email. A result means PostHog queued the email, not that +SendEmailResult | it was delivered. + +**Raises:** + +Type | Description +---- | ----------- +EmailError | PostHog did not queue the email, or the client could not tell whether it did. +(TypeError, ValueError) | An argument has the wrong type or format. + +**Examples:** + +```python +emails.send( + to="user@example.com", + template_id="0190f6d2-7c1e-7b3a-9f5e-1c2d3e4f5a6b", + template_data={"name": "Ada"}, + distinct_id="user_123", + idempotency_key="welcome:user_123", +) +``` + +#### `posthog.emails.Emails.timeout` + +```python +timeout = timeout +``` + +### `EmailError` + +```python +EmailError(*, name: str, code: str, message: str, status_code: Optional[int] = None, why: Optional[str] = None, fix: Optional[str] = None, docs_url: Optional[str] = None, field: Optional[str] = None, retry_after: Optional[float] = None) -> None +``` + +Bases: Exception + +PostHog did not queue the email, or the client could not tell whether it did. + +Branch on `code`, PostHog's stable error code, or on `name`, a coarse +category of the failure. The message never contains the secret key or the +request body. + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`name` | | A coarse category of the failure. +`code` | | PostHog's stable error code. +`message` | | What went wrong. It never contains the secret key or the request body. +`status_code` | | The HTTP status of the response, or `None` when none arrived. +`why` | | PostHog's explanation of the failure, when it gave one. +`fix` | | What to change to fix it, when PostHog said. +`docs_url` | | A docs page about the error, when PostHog gave one. +`field` | | The request field the error is about, when there is one. +`retry_after` | | Seconds to wait before retrying, when PostHog said. + +#### `posthog.emails.EmailError.code` + +```python +code = code +``` + +#### `posthog.emails.EmailError.docs_url` + +```python +docs_url = docs_url +``` + +#### `posthog.emails.EmailError.field` + +```python +field = field +``` + +#### `posthog.emails.EmailError.fix` + +```python +fix = fix +``` + +#### `posthog.emails.EmailError.message` + +```python +message = message +``` + +#### `posthog.emails.EmailError.name` + +```python +name = name +``` + +#### `posthog.emails.EmailError.retry_after` + +```python +retry_after = retry_after +``` + +#### `posthog.emails.EmailError.status_code` + +```python +status_code = status_code +``` + +#### `posthog.emails.EmailError.why` + +```python +why = why +``` + +### `SendEmailResult` + +```python +SendEmailResult(id: str, replayed: bool = False, warnings: tuple[EmailWarning, ...] = ()) -> None +``` + +The queued email. `id` is its message ID. + +**Attributes:** + +Name | Type | Description +---- | ---- | ----------- +`id` | str | The email's message ID. +`replayed` | bool | `True` when the response says PostHog replayed an earlier send for the same `idempotency_key` instead of sending again. +`warnings` | tuple[EmailWarning, ...] | Warnings PostHog returned about the request. + +#### `posthog.emails.SendEmailResult.id` + +```python +id: str +``` + +#### `posthog.emails.SendEmailResult.replayed` + +```python +replayed: bool = False +``` + +#### `posthog.emails.SendEmailResult.warnings` + +```python +warnings: tuple[EmailWarning, ...] = () +``` From 57c9359d69c4a2a6f7793396293c0a8c291fbc39 Mon Sep 17 00:00:00 2001 From: "Vincent (Wen Yu) Ge" Date: Wed, 4 Nov 2026 17:47:58 -0500 Subject: [PATCH 7/8] docs: make the griffe2md output MDX-safe Co-Authored-By: Claude Opus 5.5 --- bin/docs_scripts/generate_markdown.py | 2 + references/posthog-python-7.md | 752 ++++++++++++------------ references/posthog-python-8.md | 784 ++++++++++++------------- references/posthog-python-reference.md | 752 ++++++++++++------------ 4 files changed, 1146 insertions(+), 1144 deletions(-) diff --git a/bin/docs_scripts/generate_markdown.py b/bin/docs_scripts/generate_markdown.py index ae0474c21..6667fd4f5 100644 --- a/bin/docs_scripts/generate_markdown.py +++ b/bin/docs_scripts/generate_markdown.py @@ -50,6 +50,8 @@ def entry(obj, name: str) -> str: md = griffe2md.render_object_docs(obj, CONFIG) # Annotation links point at anchors this page doesn't have. md = re.sub(r"\[([^\]]+)\]\(#[\w.]+\)", r"\1", md) + # The site reads Markdown as MDX 1: HTML must be JSX, and braces in are expressions. + md = re.sub(r"(.*?)", r"`\1`", md.replace("
", "
")) # A parameter documented only in the docstring, like a **kwargs key, isn't required. md = re.sub(r"^(`\w+` \| \| .*) \| \*required\*$", r"\1 | ", md, flags=re.M) return re.sub(r"^### .*$", f"### `{name}`", md.strip(), count=1, flags=re.M) diff --git a/references/posthog-python-7.md b/references/posthog-python-7.md index 53276c162..bee1b6cc1 100644 --- a/references/posthog-python-7.md +++ b/references/posthog-python-7.md @@ -139,12 +139,12 @@ code never branches. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`name` | str | A low-cardinality operation name, e.g. `GET /users/:id`. Variable values belong in attributes, not the name. | *required* -`kind` | Optional[str] | `internal` (default), `server`, `client`, `producer` or `consumer`. | None -`attributes` | Optional[Mapping[str, Any]] | Initial attributes. | None -`parent` | Union[Span, str, None] | A span handle, or an inbound W3C `traceparent` header value to continue a remote trace. Defaults to the active span. A forked child starts with no active span; pass the parent span to continue a trace across a fork. | None -`tracestate` | Optional[str] | The inbound `tracestate` header accompanying a `traceparent` string `parent`; preserved and propagated. | None -`start_time` | Union[datetime, float, None] | A `datetime` or epoch seconds, to backdate the span. | None +`name` | `str` | A low-cardinality operation name, e.g. `GET /users/:id`. Variable values belong in attributes, not the name. | *required* +`kind` | `Optional[str]` | `internal` (default), `server`, `client`, `producer` or `consumer`. | `None` +`attributes` | `Optional[Mapping[str, Any]]` | Initial attributes. | `None` +`parent` | `Union[Span, str, None]` | A span handle, or an inbound W3C `traceparent` header value to continue a remote trace. Defaults to the active span. A forked child starts with no active span; pass the parent span to continue a trace across a fork. | `None` +`tracestate` | `Optional[str]` | The inbound `tracestate` header accompanying a `traceparent` string `parent`; preserved and propagated. | `None` +`start_time` | `Union[datetime, float, None]` | A `datetime` or epoch seconds, to backdate the span. | `None` **Examples:** @@ -192,11 +192,11 @@ Create an alias between two distinct IDs. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`previous_id` | ID_TYPES | The previous distinct ID. Required - the call is dropped with a warning if it is missing or empty. | *required* -`distinct_id` | Optional[str] | The new distinct ID to alias to. Falls back to the context distinct ID; the call is dropped with a warning if neither is available. | *required* -`timestamp` | Optional[Union[datetime, str]] | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | None -`uuid` | Optional[str] | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | None -`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this event. | None +`previous_id` | `ID_TYPES` | The previous distinct ID. Required - the call is dropped with a warning if it is missing or empty. | *required* +`distinct_id` | `Optional[str]` | The new distinct ID to alias to. Falls back to the context distinct ID; the call is dropped with a warning if neither is available. | *required* +`timestamp` | `Optional[Union[datetime, str]]` | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | `None` +`uuid` | `Optional[str]` | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | `None` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this event. | `None` **Examples:** @@ -220,7 +220,7 @@ Captures an event manually. [Learn about capture best practices](https://posthog Name | Type | Description | Default ---- | ---- | ----------- | ------- -`event` | str | The event name to capture. | *required* +`event` | `str` | The event name to capture. | *required* `distinct_id` | | The distinct ID of the user. | `properties` | | A dictionary of properties to include with the event. | `timestamp` | | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | @@ -298,7 +298,7 @@ span IDs are added as `$trace_id` and `$span_id` event properties. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`exception` | Optional[ExceptionArg] | The exception to capture. | *required* +`exception` | `Optional[ExceptionArg]` | The exception to capture. | *required* `distinct_id` | | The distinct ID of the user. | `properties` | | A dictionary of additional properties. Overriding reserved exception properties is deprecated and will stop working in the next major version. | `flags` | | A `FeatureFlagEvaluations` snapshot from `evaluate_flags()`. Attaches those exact flag values to the captured `$exception` event. | @@ -368,7 +368,7 @@ deferred rather than blocking the worker that invoked the callback. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`timeout_seconds` | Optional[float] | Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass `None` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently. The span exporter waits up to this many seconds for an in-flight flush, then uses a fresh drain budget once it acquires the lock. If the lock wait times out, the in-flight flush is left to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. Each span request is bounded by the client's `timeout` in seconds, and the last request is not cut short. This is therefore not a strict wall-clock limit for the whole call. With `None`, a retriable span failure is left for a later flush rather than retried within this call. | 10 +`timeout_seconds` | `Optional[float]` | Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass `None` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently. The span exporter waits up to this many seconds for an in-flight flush, then uses a fresh drain budget once it acquires the lock. If the lock wait times out, the in-flight flush is left to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. Each span request is bounded by the client's `timeout` in seconds, and the last request is not cut short. This is therefore not a strict wall-clock limit for the whole call. With `None`, a retriable span failure is left for a later flush rather than retried within this call. | `10` **Examples:** @@ -392,15 +392,15 @@ Get multivariate feature flag value for a user. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The feature flag key. | *required* -`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None -`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None -`only_evaluate_locally` | bool | Whether to only evaluate locally. | False -`send_feature_flag_events` | bool | Whether to send feature flag events. | True -`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None -`device_id` | Optional[str] | The device ID for this request. | None +`key` | `str` | The feature flag key. | *required* +`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` +`only_evaluate_locally` | `bool` | Whether to only evaluate locally. | `False` +`send_feature_flag_events` | `bool` | Whether to send feature flag events. | `True` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` +`device_id` | `Optional[str]` | The device ID for this request. | `None` **Examples:** @@ -426,24 +426,24 @@ Get the payload for a feature flag. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The feature flag key. | *required* -`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* -`match_value` | Optional[FlagValue] | The specific flag value to get payload for. | None -`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None -`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None -`only_evaluate_locally` | bool | Whether to only evaluate locally. | False -`send_feature_flag_events` | bool | Deprecated. Use get_feature_flag() instead if you need events. | False -`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None -`device_id` | Optional[str] | The device ID for this request. | None +`key` | `str` | The feature flag key. | *required* +`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* +`match_value` | `Optional[FlagValue]` | The specific flag value to get payload for. | `None` +`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` +`only_evaluate_locally` | `bool` | Whether to only evaluate locally. | `False` +`send_feature_flag_events` | `bool` | Deprecated. Use get_feature_flag() instead if you need events. | `False` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` +`device_id` | `Optional[str]` | The device ID for this request. | `None` **Returns:** Type | Description ---- | ----------- -Optional[object] | The payload associated with the matched feature flag value, or None. -Optional[object] | This method returns the payload only, not the FeatureFlagResult wrapper -Optional[object] | used internally to compute it. +`Optional[object]` | The payload associated with the matched feature flag value, or None. +`Optional[object]` | This method returns the payload only, not the FeatureFlagResult wrapper +`Optional[object]` | used internally to compute it. **Examples:** @@ -482,21 +482,21 @@ if flag_result and flag_result.get_value() == 'variant-key': Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The feature flag key. | *required* -`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None -`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None -`only_evaluate_locally` | bool | Whether to only evaluate locally. | False -`send_feature_flag_events` | bool | Whether to send feature flag events. | True -`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None -`device_id` | Optional[str] | The device ID for this request. | None +`key` | `str` | The feature flag key. | *required* +`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` +`only_evaluate_locally` | `bool` | Whether to only evaluate locally. | `False` +`send_feature_flag_events` | `bool` | Whether to send feature flag events. | `True` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` +`device_id` | `Optional[str]` | The device ID for this request. | `None` **Returns:** Type | Description ---- | ----------- -Optional[FeatureFlagResult] | Optional[FeatureFlagResult]: The feature flag result or None if disabled/not found. +`Optional[FeatureFlagResult]` | Optional[FeatureFlagResult]: The feature flag result or None if disabled/not found. ## Get feature flags @@ -512,13 +512,13 @@ Get feature flag variants for a user. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None -`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None -`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None -`flag_keys_to_evaluate` | Optional[list[str]] | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | None -`device_id` | Optional[str] | The device ID for this request. | None +`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` +`flag_keys_to_evaluate` | `Optional[list[str]]` | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | `None` +`device_id` | `Optional[str]` | The device ID for this request. | `None` ### `Client.get_all_flags` @@ -532,14 +532,14 @@ Get all feature flags for a user. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None -`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None -`only_evaluate_locally` | bool | Whether to only evaluate locally. | False -`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None -`flag_keys_to_evaluate` | Optional[list[str]] | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | None -`device_id` | Optional[str] | The device ID for this request. | None +`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` +`only_evaluate_locally` | `bool` | Whether to only evaluate locally. | `False` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` +`flag_keys_to_evaluate` | `Optional[list[str]]` | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | `None` +`device_id` | `Optional[str]` | The device ID for this request. | `None` **Examples:** @@ -561,13 +561,13 @@ Get feature flags and payloads for a user. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None -`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None -`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None -`flag_keys_to_evaluate` | Optional[list[str]] | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | None -`device_id` | Optional[str] | The device ID for this request. | None +`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` +`flag_keys_to_evaluate` | `Optional[list[str]]` | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | `None` +`device_id` | `Optional[str]` | The device ID for this request. | `None` **Examples:** @@ -587,14 +587,14 @@ Get all feature flags and their payloads for a user. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None -`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None -`only_evaluate_locally` | bool | Whether to only evaluate locally. | False -`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None -`flag_keys_to_evaluate` | Optional[list[str]] | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | None -`device_id` | Optional[str] | The device ID for this request. | None +`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` +`only_evaluate_locally` | `bool` | Whether to only evaluate locally. | `False` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` +`flag_keys_to_evaluate` | `Optional[list[str]]` | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | `None` +`device_id` | `Optional[str]` | The device ID for this request. | `None` **Examples:** @@ -627,20 +627,20 @@ and reason `"Evaluated locally"`. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`distinct_id` | Optional[ID_TYPES] | The user's distinct ID. If `None`, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot. | None -`groups` | Optional[Mapping[str, Union[str, int]]] | Mapping of group type to group key. | None -`person_properties` | Optional[Dict[str, Any]] | Person properties to use for evaluation. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties keyed by group type. | None -`only_evaluate_locally` | bool | If True, never fall back to remote evaluation — flags that can't be evaluated locally are omitted from the snapshot and listed with a reason in its `unresolved_flags`. | False -`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup. | None -`flag_keys` | Optional[List[str]] | Optional list that scopes local evaluation, the underlying `/flags` request, and the returned snapshot. When omitted or `None`, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per `evaluate_flags` call unless `only_evaluate_locally` is True. If the server also does not know the key, it is omitted from the snapshot. | None -`device_id` | Optional[str] | Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes. | None +`distinct_id` | `Optional[ID_TYPES]` | The user's distinct ID. If `None`, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot. | `None` +`groups` | `Optional[Mapping[str, Union[str, int]]]` | Mapping of group type to group key. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | Person properties to use for evaluation. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties keyed by group type. | `None` +`only_evaluate_locally` | `bool` | If True, never fall back to remote evaluation — flags that can't be evaluated locally are omitted from the snapshot and listed with a reason in its `unresolved_flags`. | `False` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup. | `None` +`flag_keys` | `Optional[List[str]]` | Optional list that scopes local evaluation, the underlying `/flags` request, and the returned snapshot. When omitted or `None`, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per `evaluate_flags` call unless `only_evaluate_locally` is True. If the server also does not know the key, it is omitted from the snapshot. | `None` +`device_id` | `Optional[str]` | Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes. | `None` **Returns:** Type | Description ---- | ----------- -FeatureFlagEvaluations | A `FeatureFlagEvaluations` snapshot. +`FeatureFlagEvaluations` | A `FeatureFlagEvaluations` snapshot. **Examples:** @@ -668,13 +668,13 @@ Identify a group and set its properties. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`group_type` | str | The type of group (e.g., 'company', 'team'). Required - the call is dropped with a warning if it is missing or empty. | *required* -`group_key` | str | The unique identifier for the group. Required - the call is dropped with a warning if it is missing or empty. | *required* -`properties` | Optional[Dict[str, Any]] | A dictionary of properties to set on the group. | None -`timestamp` | Optional[Union[datetime, str]] | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | None -`uuid` | Optional[Union[str, UUID]] | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | None -`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this event. | None -`distinct_id` | Optional[ID_TYPES] | The distinct ID of the user performing the action. | None +`group_type` | `str` | The type of group (e.g., 'company', 'team'). Required - the call is dropped with a warning if it is missing or empty. | *required* +`group_key` | `str` | The unique identifier for the group. Required - the call is dropped with a warning if it is missing or empty. | *required* +`properties` | `Optional[Dict[str, Any]]` | A dictionary of properties to set on the group. | `None` +`timestamp` | `Optional[Union[datetime, str]]` | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | `None` +`uuid` | `Optional[Union[str, UUID]]` | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | `None` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this event. | `None` +`distinct_id` | `Optional[ID_TYPES]` | The distinct ID of the user performing the action. | `None` **Examples:** @@ -756,15 +756,15 @@ Check if a feature flag is enabled for a user. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The feature flag key. | *required* -`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None -`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None -`only_evaluate_locally` | bool | Whether to only evaluate locally. | False -`send_feature_flag_events` | bool | Whether to send feature flag events. | True -`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None -`device_id` | Optional[str] | The device ID for this request. | None +`key` | `str` | The feature flag key. | *required* +`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` +`only_evaluate_locally` | `bool` | Whether to only evaluate locally. | `False` +`send_feature_flag_events` | `bool` | Whether to send feature flag events. | `True` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` +`device_id` | `Optional[str]` | The device ID for this request. | `None` **Examples:** @@ -790,55 +790,55 @@ Initialize a new PostHog client instance. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`project_api_key` | str | PostHog project API key/token. | *required* -`host` | | PostHog host. Defaults to the US ingestion endpoint when not set. App hosts such as `https://us.posthog.com` are mapped to the corresponding ingestion host. | None -`debug` | | Enable verbose SDK logging and re-raise errors from public API methods. | False -`max_queue_size` | | Maximum number of events buffered before upload. | 10000 -`send` | | If False, queueing succeeds but events are not sent. | True -`on_error` | | Optional callback invoked by background consumers when an upload fails. Keep it short and non-blocking. Calling lifecycle methods directly is safe and deferred, but do not start another thread or task that calls `flush()`, `join()`, or `shutdown()` and then wait for it from the callback. | None -`flush_at` | | Number of queued events that triggers a batch upload. | 100 -`flush_interval` | | Maximum seconds a background consumer waits before flushing a partial batch. | 5.0 -`gzip` | | Whether to gzip event upload payloads. | False -`max_retries` | | Number of upload retries. Values below 0 are treated as 0. | 3 -`sync_mode` | | If True, send each event synchronously instead of using background worker threads. This blocks the calling thread; in asyncio applications such as FastAPI, use `AsyncPosthog` instead. | False -`timeout` | float | HTTP request timeout in seconds for event uploads. | 15 -`thread` | | Number of background consumer threads. | 1 -`poll_interval` | | Seconds between local feature flag definition refreshes. | 30 -`secret_key` | | A Personal API Key or Project Secret API Key, used to authenticate local feature flag evaluation, remote config payloads, and decrypted flag payloads. Example::
posthog.Client(project_api_key, secret_key="phx_...") | None -`personal_api_key` | | Deprecated alias for `secret_key`. Still honored for backwards compatibility; prefer `secret_key`, which also accepts a Project Secret API Key. | None -`disabled` | | If True, disable captures and API requests. Useful in tests. | False -`disable_geoip` | | Whether to disable server-side GeoIP enrichment. Defaults to True. | True -`is_server` | | Whether events are emitted from a server-side runtime. Defaults to True; set to False when using the SDK as a client/CLI so the device OS is attributed to the person normally. | True -`historical_migration` | | Mark events as historical migration imports. | False -`feature_flags_request_timeout_seconds` | float | Timeout in seconds for feature flag and remote config requests. | 3 -`feature_flags_request_max_retries` | | Number of retries for feature flag requests after network, transport, or timeout failures. Defaults to 1. Set to 0 to disable retries. | 1 -`app_host` | Optional[str] | Your PostHog app host, such as `https://posthog.example.com`. `emails` sends there. Needed only when `host` is not PostHog Cloud, for example a reverse proxy, which must not receive the secret key. | None -`email_request_timeout_seconds` | float | Timeout in seconds for each email send request. Defaults to 15. | 15 -`email_request_max_retries` | int | Number of retries for an email send after a network error, a rate limit, or an unavailable service. Defaults to 2. Set to 0 when a job runner already retries the send. | 2 -`super_properties` | | Properties merged into every captured event. | None -`enable_exception_autocapture` | | Automatically capture uncaught exceptions. | False -`log_captured_exceptions` | | Also log exceptions captured by error tracking. | False -`project_root` | | Root path used to determine in-app stack frames for captured exceptions. Defaults to the current working directory. | None -`privacy_mode` | | For AI observability, capture usage metadata without prompt inputs or outputs. | False -`enable_full_ai_capture` | | Route PostHog AI wrapper events through the dedicated AI capture endpoint and capture full AI content: skips string truncation and passes media (base64/data URIs) through unredacted. `privacy_mode` always wins. Defaults to False. | False -`before_send` | | Optional callback that can modify or drop events before upload. Return `None` to drop an event. | None -`flag_fallback_cache_url` | | Optional feature flag fallback cache URL, such as `memory://local/?ttl=300&size=10000` or a Redis URL. | None -`enable_local_evaluation` | | Whether to poll feature flag definitions for local evaluation when a personal API key is configured. | True -`flag_definition_cache_provider` | Optional[FlagDefinitionCacheProvider] | Optional external cache provider for sharing feature flag definitions across workers. | None -`capture_exception_code_variables` | | Capture local variable values on exception stack frames. | False -`capture_trace_context` | | When OpenTelemetry is installed and a valid span is active at capture time, add its trace and span IDs as `$trace_id` and `$span_id` properties to events captured with `capture()` and `capture_ai()`, so they can be correlated with backend traces. Explicit `$trace_id`/`$span_id` values passed in `properties` win. Exception events (`capture_exception`) always attach these IDs regardless of this setting. Defaults to False. | False -`traces` | Optional[dict] | Config dict for distributed tracing: `service_name`, `service_version`, `environment`, `resource_attributes`, `flush_interval` (5 s), `max_queue_size` (2048), `max_export_batch_size` (512), `max_live_spans` (10000), `max_span_age` (3600 s), `max_attributes_per_span` (128), `max_events_per_span` (128), `max_attribute_value_length` (8192). `before_span_send` is a callable, or a list run in order, that receives each finished span as a dict (`trace_id`, `span_id` and `parent_span_id` are read-only) and returns it, edited, or `None` to drop it; a hook that raises drops the span. Tracing is off until this is provided. Spans export on a background timer even with `sync_mode`; serverless handlers should call `flush()` before returning. Defaults to None. | None -`code_variables_mask_patterns` | | Variable-name patterns to mask when capturing code variables. | None -`code_variables_ignore_patterns` | | Variable-name patterns to omit when capturing code variables. | None -`code_variables_mask_url_credentials` | | Scrub credentials embedded in URLs/DSNs (e.g. `user:pass@host`) from captured code variables, regardless of the surrounding variable name. Defaults to True. | None -`code_variables_detect_secrets` | | Last-resort entropy-based detection that redacts high-entropy secret-looking values (API keys, tokens, strong passwords) sitting in innocuously-named variables, after the name and URL checks. Skips structured ids (UUIDs, ObjectIds, hashes). Defaults to True. | None -`in_app_modules` | list[str] \| None | Module/package prefixes treated as in-app frames in captured exceptions. | None -`enable_exception_autocapture_rate_limiting` | | Rate limit autocaptured exceptions client-side with a token bucket per exception type. Disabled by default. | False -`exception_autocapture_bucket_size` | | Maximum burst of autocaptured exceptions allowed per exception type (token bucket size, clamped to 0-100). | DEFAULT_BUCKET_SIZE -`exception_autocapture_refill_rate` | | Tokens restored per refill interval for each exception type's bucket. | DEFAULT_REFILL_RATE -`exception_autocapture_refill_interval_seconds` | | Seconds between token refills for autocaptured exception rate limiting. | DEFAULT_REFILL_INTERVAL_SECONDS -`capture_mode` | Optional[Union[CaptureMode, str]] | Capture wire protocol to use. Defaults to `CaptureMode.V0` (legacy `/batch/`). Set `CaptureMode.V1` (or pass the string `"v1"`) to opt into `/i/v1/analytics/events`. When omitted, the `POSTHOG_CAPTURE_MODE` env var is consulted, then `V0`. | None -`capture_compression` | Optional[Union[CaptureCompression, str]] | Request-body compression for capture-v1 uploads (ignored in V0, which uses `gzip`). `CaptureCompression.GZIP` or `DEFLATE` (or the strings `"gzip"`/`"deflate"`). When omitted, the `POSTHOG_CAPTURE_COMPRESSION` env var is consulted, then the legacy `gzip` flag, then no compression. | None +`project_api_key` | `str` | PostHog project API key/token. | *required* +`host` | | PostHog host. Defaults to the US ingestion endpoint when not set. App hosts such as `https://us.posthog.com` are mapped to the corresponding ingestion host. | `None` +`debug` | | Enable verbose SDK logging and re-raise errors from public API methods. | `False` +`max_queue_size` | | Maximum number of events buffered before upload. | `10000` +`send` | | If False, queueing succeeds but events are not sent. | `True` +`on_error` | | Optional callback invoked by background consumers when an upload fails. Keep it short and non-blocking. Calling lifecycle methods directly is safe and deferred, but do not start another thread or task that calls `flush()`, `join()`, or `shutdown()` and then wait for it from the callback. | `None` +`flush_at` | | Number of queued events that triggers a batch upload. | `100` +`flush_interval` | | Maximum seconds a background consumer waits before flushing a partial batch. | `5.0` +`gzip` | | Whether to gzip event upload payloads. | `False` +`max_retries` | | Number of upload retries. Values below 0 are treated as 0. | `3` +`sync_mode` | | If True, send each event synchronously instead of using background worker threads. This blocks the calling thread; in asyncio applications such as FastAPI, use `AsyncPosthog` instead. | `False` +`timeout` | `float` | HTTP request timeout in seconds for event uploads. | `15` +`thread` | | Number of background consumer threads. | `1` +`poll_interval` | | Seconds between local feature flag definition refreshes. | `30` +`secret_key` | | A Personal API Key or Project Secret API Key, used to authenticate local feature flag evaluation, remote config payloads, and decrypted flag payloads. Example::
posthog.Client(project_api_key, secret_key="phx_...") | `None` +`personal_api_key` | | Deprecated alias for `secret_key`. Still honored for backwards compatibility; prefer `secret_key`, which also accepts a Project Secret API Key. | `None` +`disabled` | | If True, disable captures and API requests. Useful in tests. | `False` +`disable_geoip` | | Whether to disable server-side GeoIP enrichment. Defaults to True. | `True` +`is_server` | | Whether events are emitted from a server-side runtime. Defaults to True; set to False when using the SDK as a client/CLI so the device OS is attributed to the person normally. | `True` +`historical_migration` | | Mark events as historical migration imports. | `False` +`feature_flags_request_timeout_seconds` | `float` | Timeout in seconds for feature flag and remote config requests. | `3` +`feature_flags_request_max_retries` | | Number of retries for feature flag requests after network, transport, or timeout failures. Defaults to 1. Set to 0 to disable retries. | `1` +`app_host` | `Optional[str]` | Your PostHog app host, such as `https://posthog.example.com`. `emails` sends there. Needed only when `host` is not PostHog Cloud, for example a reverse proxy, which must not receive the secret key. | `None` +`email_request_timeout_seconds` | `float` | Timeout in seconds for each email send request. Defaults to 15. | `15` +`email_request_max_retries` | `int` | Number of retries for an email send after a network error, a rate limit, or an unavailable service. Defaults to 2. Set to 0 when a job runner already retries the send. | `2` +`super_properties` | | Properties merged into every captured event. | `None` +`enable_exception_autocapture` | | Automatically capture uncaught exceptions. | `False` +`log_captured_exceptions` | | Also log exceptions captured by error tracking. | `False` +`project_root` | | Root path used to determine in-app stack frames for captured exceptions. Defaults to the current working directory. | `None` +`privacy_mode` | | For AI observability, capture usage metadata without prompt inputs or outputs. | `False` +`enable_full_ai_capture` | | Route PostHog AI wrapper events through the dedicated AI capture endpoint and capture full AI content: skips string truncation and passes media (base64/data URIs) through unredacted. `privacy_mode` always wins. Defaults to False. | `False` +`before_send` | | Optional callback that can modify or drop events before upload. Return `None` to drop an event. | `None` +`flag_fallback_cache_url` | | Optional feature flag fallback cache URL, such as `memory://local/?ttl=300&size=10000` or a Redis URL. | `None` +`enable_local_evaluation` | | Whether to poll feature flag definitions for local evaluation when a personal API key is configured. | `True` +`flag_definition_cache_provider` | `Optional[FlagDefinitionCacheProvider]` | Optional external cache provider for sharing feature flag definitions across workers. | `None` +`capture_exception_code_variables` | | Capture local variable values on exception stack frames. | `False` +`capture_trace_context` | | When OpenTelemetry is installed and a valid span is active at capture time, add its trace and span IDs as `$trace_id` and `$span_id` properties to events captured with `capture()` and `capture_ai()`, so they can be correlated with backend traces. Explicit `$trace_id`/`$span_id` values passed in `properties` win. Exception events (`capture_exception`) always attach these IDs regardless of this setting. Defaults to False. | `False` +`traces` | `Optional[dict]` | Config dict for distributed tracing: `service_name`, `service_version`, `environment`, `resource_attributes`, `flush_interval` (5 s), `max_queue_size` (2048), `max_export_batch_size` (512), `max_live_spans` (10000), `max_span_age` (3600 s), `max_attributes_per_span` (128), `max_events_per_span` (128), `max_attribute_value_length` (8192). `before_span_send` is a callable, or a list run in order, that receives each finished span as a dict (`trace_id`, `span_id` and `parent_span_id` are read-only) and returns it, edited, or `None` to drop it; a hook that raises drops the span. Tracing is off until this is provided. Spans export on a background timer even with `sync_mode`; serverless handlers should call `flush()` before returning. Defaults to None. | `None` +`code_variables_mask_patterns` | | Variable-name patterns to mask when capturing code variables. | `None` +`code_variables_ignore_patterns` | | Variable-name patterns to omit when capturing code variables. | `None` +`code_variables_mask_url_credentials` | | Scrub credentials embedded in URLs/DSNs (e.g. `user:pass@host`) from captured code variables, regardless of the surrounding variable name. Defaults to True. | `None` +`code_variables_detect_secrets` | | Last-resort entropy-based detection that redacts high-entropy secret-looking values (API keys, tokens, strong passwords) sitting in innocuously-named variables, after the name and URL checks. Skips structured ids (UUIDs, ObjectIds, hashes). Defaults to True. | `None` +`in_app_modules` | `list[str] \| None` | Module/package prefixes treated as in-app frames in captured exceptions. | `None` +`enable_exception_autocapture_rate_limiting` | | Rate limit autocaptured exceptions client-side with a token bucket per exception type. Disabled by default. | `False` +`exception_autocapture_bucket_size` | | Maximum burst of autocaptured exceptions allowed per exception type (token bucket size, clamped to 0-100). | `DEFAULT_BUCKET_SIZE` +`exception_autocapture_refill_rate` | | Tokens restored per refill interval for each exception type's bucket. | `DEFAULT_REFILL_RATE` +`exception_autocapture_refill_interval_seconds` | | Seconds between token refills for autocaptured exception rate limiting. | `DEFAULT_REFILL_INTERVAL_SECONDS` +`capture_mode` | `Optional[Union[CaptureMode, str]]` | Capture wire protocol to use. Defaults to `CaptureMode.V0` (legacy `/batch/`). Set `CaptureMode.V1` (or pass the string `"v1"`) to opt into `/i/v1/analytics/events`. When omitted, the `POSTHOG_CAPTURE_MODE` env var is consulted, then `V0`. | `None` +`capture_compression` | `Optional[Union[CaptureCompression, str]]` | Request-body compression for capture-v1 uploads (ignored in V0, which uses `gzip`). `CaptureCompression.GZIP` or `DEFLATE` (or the strings `"gzip"`/`"deflate"`). When omitted, the `POSTHOG_CAPTURE_COMPRESSION` env var is consulted, then the legacy `gzip` flag, then no compression. | `None` **Examples:** @@ -966,8 +966,8 @@ Create a new context for managing shared state. Learn more about [contexts](/doc Name | Type | Description | Default ---- | ---- | ----------- | ------- -`fresh` | | Whether to create a fresh context that doesn't inherit from parent. | False -`capture_exceptions` | Optional[bool] | Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting. | None +`fresh` | | Whether to create a fresh context that doesn't inherit from parent. | `False` +`capture_exceptions` | `Optional[bool]` | Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting. | `None` **Examples:** @@ -996,8 +996,8 @@ Decorator that creates a new context for the wrapped function using this client. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`fresh` | | Whether to create a fresh context that doesn't inherit from parent. | False -`capture_exceptions` | Optional[bool] | Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting. | None +`fresh` | | Whether to create a fresh context that doesn't inherit from parent. | `False` +`capture_exceptions` | `Optional[bool]` | Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting. | `None`
Category @@ -1018,8 +1018,8 @@ Add a tag to the current context. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`name` | str | The tag key. | *required* -`value` | Any | The tag value. | *required* +`name` | `str` | The tag key. | *required* +`value` | `Any` | The tag value. | *required*
Category @@ -1040,7 +1040,7 @@ Get all tags from the current context. Type | Description ---- | ----------- -Dict[str, Any] | Dict of all tags in the current context. +`Dict[str, Any]` | Dict of all tags in the current context.
Category @@ -1061,7 +1061,7 @@ Identify the current context with a distinct ID. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`distinct_id` | str | The distinct ID to associate with the current context and its children. | *required* +`distinct_id` | `str` | The distinct ID to associate with the current context and its children. | *required*
Category @@ -1082,7 +1082,7 @@ Set the session ID for the current context. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`session_id` | str | The session ID to associate with the current context and its children. | *required* +`session_id` | `str` | The session ID to associate with the current context and its children. | *required*
Category @@ -1103,7 +1103,7 @@ Set the device ID for the current context. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`device_id` | str | The device ID to associate with the current context and its children. | *required* +`device_id` | `str` | The device ID to associate with the current context and its children. | *required*
Category @@ -1132,13 +1132,13 @@ Get feature flag payloads for a user, preserving valid serialized JSON. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None -`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None -`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None -`flag_keys_to_evaluate` | Optional[list[str]] | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | None -`device_id` | Optional[str] | The device ID for this request. | None +`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` +`flag_keys_to_evaluate` | `Optional[list[str]]` | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | `None` +`device_id` | `Optional[str]` | The device ID for this request. | `None` **Examples:** @@ -1165,13 +1165,13 @@ Get feature flags decision. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`distinct_id` | Optional[ID_TYPES] | The distinct ID of the user. | None -`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None -`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None -`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None -`flag_keys_to_evaluate` | Optional[list[str]] | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | None -`device_id` | Optional[str] | The device ID for this request. | None +`distinct_id` | `Optional[ID_TYPES]` | The distinct ID of the user. | `None` +`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` +`flag_keys_to_evaluate` | `Optional[list[str]]` | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | `None` +`device_id` | `Optional[str]` | The device ID for this request. | `None` **Examples:** @@ -1220,7 +1220,7 @@ Get the payload for a remote config feature flag. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The remote config feature flag key. | *required* +`key` | `str` | The remote config feature flag key. | *required* **Returns:** @@ -1257,16 +1257,16 @@ Return where a locally loaded feature flag is meant to be evaluated. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The feature flag key. | *required* +`key` | `str` | The feature flag key. | *required* **Returns:** Type | Description ---- | ----------- -Optional[FeatureFlagEvaluationRuntime] | The flag's evaluation runtime, or `None` when local evaluation has -Optional[FeatureFlagEvaluationRuntime] | not loaded a definition for this key. A definition that carries no -Optional[FeatureFlagEvaluationRuntime] | runtime reports `FeatureFlagEvaluationRuntime.ALL`, the default -Optional[FeatureFlagEvaluationRuntime] | PostHog applies. +`Optional[FeatureFlagEvaluationRuntime]` | The flag's evaluation runtime, or `None` when local evaluation has +`Optional[FeatureFlagEvaluationRuntime]` | not loaded a definition for this key. A definition that carries no +`Optional[FeatureFlagEvaluationRuntime]` | runtime reports `FeatureFlagEvaluationRuntime.ALL`, the default +`Optional[FeatureFlagEvaluationRuntime]` | PostHog applies. **Examples:** @@ -1302,14 +1302,14 @@ to a browser when a backend serves flags to its own frontend. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`evaluation_runtime` | Union[FeatureFlagEvaluationRuntime, str] | The runtime to match, as a `FeatureFlagEvaluationRuntime` or its string value. | *required* +`evaluation_runtime` | `Union[FeatureFlagEvaluationRuntime, str]` | The runtime to match, as a `FeatureFlagEvaluationRuntime` or its string value. | *required* **Returns:** Type | Description ---- | ----------- -list[str] | The matching flag keys, in the order local evaluation loaded them. -list[str] | Empty when no definitions are loaded. +`list[str]` | The matching flag keys, in the order local evaluation loaded them. +`list[str]` | Empty when no definitions are loaded. **Examples:** @@ -1395,7 +1395,7 @@ Name | Description Name | Type | Description ---- | ---- | ----------- `async_capable` | | -`capture_exceptions` | Optional[bool] | +`capture_exceptions` | `Optional[bool]` | `capture_tracing_headers` | | `client` | | `extra_properties` | | @@ -1405,7 +1405,7 @@ Name | Type | Description `request_filter` | | `sync_capable` | | `tag_map` | | Compatibility alias for `properties_map`. -`trust_tracing_headers` | bool | Deprecated alias for `capture_tracing_headers`. +`trust_tracing_headers` | `bool` | Deprecated alias for `capture_tracing_headers`. Initialize the middleware with Django's next handler. @@ -1580,13 +1580,13 @@ Add PostHog request context and exception tracking to a Flask app. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`app` | Optional[Flask] | An optional Flask application. If omitted, call `init_app` later (the Flask application-factory pattern). | None -`client` | Optional[Client] | Optional destination for automatically captured exceptions. The global client is used by default. Request context still enriches events captured through any client and does not redirect capture calls. | None -`capture_exceptions` | Optional[bool] | Capture exceptions that reach Flask's unhandled exception machinery. If omitted, inherit the effective client's `enable_exception_autocapture` setting. Pass `True` or `False` to override it. | None -`request_filter` | Optional[Callable[[Request], bool]] | Optional callback receiving Flask's request object. A false return value disables both context and exception capture for that request. | None -`extra_properties` | Optional[Callable[[Request], Mapping[str, Any]]] | Optional callback returning additional event properties. This is useful for application-specific metadata such as an authenticated user's role. Values should not contain secrets or request bodies. | None -`capture_tracing_headers` | Optional[bool] | Read PostHog distinct and session ID headers as request-scoped analytics context. Defaults to True. Explicit capture IDs and event properties take precedence. Never use these values for security. | None -`trust_tracing_headers` | Optional[bool] | Deprecated alias for `capture_tracing_headers`. The capture-named option takes precedence when both are supplied. | None +`app` | `Optional[Flask]` | An optional Flask application. If omitted, call `init_app` later (the Flask application-factory pattern). | `None` +`client` | `Optional[Client]` | Optional destination for automatically captured exceptions. The global client is used by default. Request context still enriches events captured through any client and does not redirect capture calls. | `None` +`capture_exceptions` | `Optional[bool]` | Capture exceptions that reach Flask's unhandled exception machinery. If omitted, inherit the effective client's `enable_exception_autocapture` setting. Pass `True` or `False` to override it. | `None` +`request_filter` | `Optional[Callable[[Request], bool]]` | Optional callback receiving Flask's request object. A false return value disables both context and exception capture for that request. | `None` +`extra_properties` | `Optional[Callable[[Request], Mapping[str, Any]]]` | Optional callback returning additional event properties. This is useful for application-specific metadata such as an authenticated user's role. Values should not contain secrets or request bodies. | `None` +`capture_tracing_headers` | `Optional[bool]` | Read PostHog distinct and session ID headers as request-scoped analytics context. Defaults to True. Explicit capture IDs and event properties take precedence. Never use these values for security. | `None` +`trust_tracing_headers` | `Optional[bool]` | Deprecated alias for `capture_tracing_headers`. The capture-named option takes precedence when both are supplied. | `None` The integration intentionally does not capture exceptions handled by an application error handler, expected HTTP exceptions, request or response @@ -1609,7 +1609,7 @@ Name | Type | Description `client` | | `extra_properties` | | `request_filter` | | -`trust_tracing_headers` | bool | Deprecated alias for `capture_tracing_headers`. +`trust_tracing_headers` | `bool` | Deprecated alias for `capture_tracing_headers`. #### `posthog.integrations.flask.PosthogFlaskIntegration.capture_exceptions` @@ -1669,13 +1669,13 @@ Add PostHog context and automatic exception capture to an ASGI app. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`app` | _ASGIApp | The downstream ASGI application. | *required* -`client` | Optional[Client] | Optional destination for automatically captured exceptions. The global client is used by default. Request context still enriches events captured through any client and does not redirect capture calls. | None -`capture_exceptions` | Optional[bool] | Capture exceptions escaping the downstream app. If omitted, inherit the effective client's `enable_exception_autocapture` setting. Pass `True` or `False` to override it. | None -`request_filter` | Optional[_RequestFilter] | Optional sync or async callback receiving the ASGI scope. Returning `False` bypasses all instrumentation for that scope. | None -`extra_properties` | Optional[_ExtraProperties] | Optional sync or async callback receiving the ASGI scope and returning additional event properties. | None -`capture_tracing_headers` | Optional[bool] | Read PostHog distinct and session ID headers as request-scoped analytics context. Defaults to True. Explicit capture IDs and event properties take precedence. Never use these values for security. | None -`trust_tracing_headers` | Optional[bool] | Deprecated alias for `capture_tracing_headers`. The capture-named option takes precedence when both are supplied. | None +`app` | `_ASGIApp` | The downstream ASGI application. | *required* +`client` | `Optional[Client]` | Optional destination for automatically captured exceptions. The global client is used by default. Request context still enriches events captured through any client and does not redirect capture calls. | `None` +`capture_exceptions` | `Optional[bool]` | Capture exceptions escaping the downstream app. If omitted, inherit the effective client's `enable_exception_autocapture` setting. Pass `True` or `False` to override it. | `None` +`request_filter` | `Optional[_RequestFilter]` | Optional sync or async callback receiving the ASGI scope. Returning `False` bypasses all instrumentation for that scope. | `None` +`extra_properties` | `Optional[_ExtraProperties]` | Optional sync or async callback receiving the ASGI scope and returning additional event properties. | `None` +`capture_tracing_headers` | `Optional[bool]` | Read PostHog distinct and session ID headers as request-scoped analytics context. Defaults to True. Explicit capture IDs and event properties take precedence. Never use these values for security. | `None` +`trust_tracing_headers` | `Optional[bool]` | Deprecated alias for `capture_tracing_headers`. The capture-named option takes precedence when both are supplied. | `None` **Attributes:** @@ -1687,7 +1687,7 @@ Name | Type | Description `client` | | `extra_properties` | | `request_filter` | | -`trust_tracing_headers` | bool | Deprecated alias for `capture_tracing_headers`. +`trust_tracing_headers` | `bool` | Deprecated alias for `capture_tracing_headers`. #### `posthog.integrations.asgi.PosthogASGIMiddleware.app` @@ -1745,11 +1745,11 @@ Celery integration that captures task lifecycle events and exceptions. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`client` | Optional[Client] | Optional `Client` instance. When provided, all events and exceptions are captured through this client rather than the global `posthog` module. Don't skip this if using a custom flag definition cache provider, and pass the custom `Client` instance here initialized with the custom provider so fork safety for that provider is handled correctly. | None -`capture_exceptions` | bool | Whether to capture task exceptions via `capture_exception` (default `True`). | True -`capture_task_lifecycle_events` | bool | Whether to emit lifecycle events of the task such as "started", "success", "failure" etc. (default `True`). | True -`propagate_context` | bool | Whether to propagate PostHog context (distinct ID, session ID, tags) from the producer to the worker via task headers (default `True`). | True -`task_filter` | Optional[Callable[[Optional[str], dict[str, Any]], bool]] | Optional callback `(task_name, task_properties) -> bool` expected to return `False` if a given task should not be tracked. | None +`client` | `Optional[Client]` | Optional `Client` instance. When provided, all events and exceptions are captured through this client rather than the global `posthog` module. Don't skip this if using a custom flag definition cache provider, and pass the custom `Client` instance here initialized with the custom provider so fork safety for that provider is handled correctly. | `None` +`capture_exceptions` | `bool` | Whether to capture task exceptions via `capture_exception` (default `True`). | `True` +`capture_task_lifecycle_events` | `bool` | Whether to emit lifecycle events of the task such as "started", "success", "failure" etc. (default `True`). | `True` +`propagate_context` | `bool` | Whether to propagate PostHog context (distinct ID, session ID, tags) from the producer to the worker via task headers (default `True`). | `True` +`task_filter` | `Optional[Callable[[Optional[str], dict[str, Any]], bool]]` | Optional callback `(task_name, task_properties) -> bool` expected to return `False` if a given task should not be tracked. | `None` **Functions:** @@ -1853,7 +1853,7 @@ to reset to the default socket behavior. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`socket_options` | Optional[SocketOptions] | A list of `(level, option, value)` tuples accepted by urllib3/`socket.setsockopt()`, or `None` to reset defaults. | *required* +`socket_options` | `Optional[SocketOptions]` | A list of `(level, option, value)` tuples accepted by urllib3/`socket.setsockopt()`, or `None` to reset defaults. | *required*
Example @@ -2022,9 +2022,9 @@ Create a new context scope that will be active for the duration of the with bloc Name | Type | Description | Default ---- | ---- | ----------- | ------- -`fresh` | bool | Whether to start with a fresh context (default: False) | False -`capture_exceptions` | Optional[bool] | Whether to capture exceptions raised within the context. If omitted, defaults to the relevant client's exception autocapture setting. | None -`client` | Optional[Client] | Optional Posthog client instance to use for this context (default: None) | None +`fresh` | `bool` | Whether to start with a fresh context (default: False) | `False` +`capture_exceptions` | `Optional[bool]` | Whether to capture exceptions raised within the context. If omitted, defaults to the relevant client's exception autocapture setting. | `None` +`client` | `Optional[Client]` | Optional Posthog client instance to use for this context (default: None) | `None` **Examples:** @@ -2054,8 +2054,8 @@ Decorator that creates a new context for the function. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`fresh` | | Whether to start with a fresh context (default: False) | False -`capture_exceptions` | Optional[bool] | Whether to capture and track exceptions with posthog error tracking. If omitted, defaults to the global exception autocapture setting. | None +`fresh` | | Whether to start with a fresh context (default: False) | `False` +`capture_exceptions` | `Optional[bool]` | Whether to capture and track exceptions with posthog error tracking. If omitted, defaults to the global exception autocapture setting. | `None` **Examples:** @@ -2086,7 +2086,7 @@ Set the session ID for the current context. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`session_id` | str | The session ID to associate with the current context and its children | *required* +`session_id` | `str` | The session ID to associate with the current context and its children | *required* **Examples:** @@ -2115,7 +2115,7 @@ in this or child contexts with the given device ID. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`device_id` | str | The device ID to associate with the current context and its children | *required* +`device_id` | `str` | The device ID to associate with the current context and its children | *required* **Examples:** @@ -2143,7 +2143,7 @@ Identify the current context with a distinct ID. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`distinct_id` | str | The distinct ID to associate with the current context and its children | *required* +`distinct_id` | `str` | The distinct ID to associate with the current context and its children | *required* **Examples:** @@ -2171,7 +2171,7 @@ Override code-variable capture for exceptions in the current context. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`enabled` | bool | Whether exceptions captured in this context should include local variable values from stack frames. | *required* +`enabled` | `bool` | Whether exceptions captured in this context should include local variable values from stack frames. | *required*
Category @@ -2192,7 +2192,7 @@ Override code-variable mask patterns for exceptions in the current context. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`mask_patterns` | list[str] | Variable-name patterns whose values should be replaced with `***` when code variables are captured. | *required* +`mask_patterns` | `list[str]` | Variable-name patterns whose values should be replaced with `***` when code variables are captured. | *required*
Category @@ -2213,7 +2213,7 @@ Override code-variable ignore patterns for exceptions in the current context. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`ignore_patterns` | list[str] | Variable-name patterns that should be omitted entirely when code variables are captured. | *required* +`ignore_patterns` | `list[str]` | Variable-name patterns that should be omitted entirely when code variables are captured. | *required*
Category @@ -2260,8 +2260,8 @@ Add a tag to the current context. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`name` | str | The tag key | *required* -`value` | Any | The tag value | *required* +`name` | `str` | The tag key | *required* +`value` | `Any` | The tag value | *required* **Examples:** @@ -2289,7 +2289,7 @@ Get all tags from the current context. Type | Description ---- | ----------- -Dict[str, Any] | Dict of all tags in the current context +`Dict[str, Any]` | Dict of all tags in the current context
Category @@ -2310,8 +2310,8 @@ Capture anything a user does within your system. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`event` | str | The event name to specify the event | *required* -`**kwargs` | Unpack[OptionalCaptureArgs] | Optional arguments including: distinct_id: Unique identifier for the user properties: Dict of event properties timestamp: When the event occurred. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. uuid: Unique identifier for this event. If omitted, one is generated and returned. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. groups: Dict of group types and IDs flags: A FeatureFlagEvaluations snapshot from evaluate_flags(). The exact values from the snapshot are attached with no extra /flags request. send_feature_flags: Deprecated. Prefer flags=... from evaluate_flags(). When truthy, evaluates flags during capture and attaches them to the event. disable_geoip: Whether to disable GeoIP lookup | {} +`event` | `str` | The event name to specify the event | *required* +`**kwargs` | `Unpack[OptionalCaptureArgs]` | Optional arguments including: distinct_id: Unique identifier for the user properties: Dict of event properties timestamp: When the event occurred. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. uuid: Unique identifier for this event. If omitted, one is generated and returned. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. groups: Dict of group types and IDs flags: A FeatureFlagEvaluations snapshot from evaluate_flags(). The exact values from the snapshot are attached with no extra /flags request. send_feature_flags: Deprecated. Prefer flags=... from evaluate_flags(). When truthy, evaluates flags during capture and attaches them to the event. disable_geoip: Whether to disable GeoIP lookup | `{}`
Details @@ -2384,8 +2384,8 @@ redaction or truncation is applied here. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`event` | str | The event name, normally one of the `$ai_*` event names. | *required* -`**kwargs` | Unpack[OptionalCaptureArgs] | Same optional arguments as `capture()`. | {} +`event` | `str` | The event name, normally one of the `$ai_*` event names. | *required* +`**kwargs` | `Unpack[OptionalCaptureArgs]` | Same optional arguments as `capture()`. | `{}` **Examples:** @@ -2411,7 +2411,7 @@ Set properties on a user record. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`**kwargs` | Unpack[OptionalSetArgs] | Optional arguments including: distinct_id: Unique identifier for the user. Falls back to the context distinct ID; if none exists, this call does nothing. properties: Dict of person properties to set. timestamp: When the properties were set. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. uuid: Unique identifier for this operation. If omitted, one is generated and returned. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. disable_geoip: Whether to disable GeoIP lookup. | {} +`**kwargs` | `Unpack[OptionalSetArgs]` | Optional arguments including: distinct_id: Unique identifier for the user. Falls back to the context distinct ID; if none exists, this call does nothing. properties: Dict of person properties to set. timestamp: When the properties were set. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. uuid: Unique identifier for this operation. If omitted, one is generated and returned. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. disable_geoip: Whether to disable GeoIP lookup. | `{}`
Details @@ -2440,7 +2440,7 @@ Set properties on a user record, only if they do not yet exist. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`**kwargs` | Unpack[OptionalSetArgs] | Optional arguments including: distinct_id: Unique identifier for the user. Falls back to the context distinct ID; if none exists, this call does nothing. properties: Dict of person properties to set only once. timestamp: When the properties were set. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. uuid: Unique identifier for this operation. If omitted, one is generated and returned. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. disable_geoip: Whether to disable GeoIP lookup. | {} +`**kwargs` | `Unpack[OptionalSetArgs]` | Optional arguments including: distinct_id: Unique identifier for the user. Falls back to the context distinct ID; if none exists, this call does nothing. properties: Dict of person properties to set only once. timestamp: When the properties were set. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. uuid: Unique identifier for this operation. If omitted, one is generated and returned. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. disable_geoip: Whether to disable GeoIP lookup. | `{}`
Details @@ -2470,13 +2470,13 @@ Set properties on a group. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`group_type` | str | Type of your group. Required - the call is dropped with a warning if it is missing or empty. | *required* -`group_key` | str | Unique identifier of the group. Required - the call is dropped with a warning if it is missing or empty. | *required* -`properties` | Optional[Dict[str, Any]] | Properties to set on the group | None -`timestamp` | Optional[Union[datetime, str]] | Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | None -`uuid` | Optional[str] | Optional UUID for the event | None -`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup | None -`distinct_id` | Optional[ID_TYPES] | Optional distinct ID of the user performing the action | None +`group_type` | `str` | Type of your group. Required - the call is dropped with a warning if it is missing or empty. | *required* +`group_key` | `str` | Unique identifier of the group. Required - the call is dropped with a warning if it is missing or empty. | *required* +`properties` | `Optional[Dict[str, Any]]` | Properties to set on the group | `None` +`timestamp` | `Optional[Union[datetime, str]]` | Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | `None` +`uuid` | `Optional[str]` | Optional UUID for the event | `None` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup | `None` +`distinct_id` | `Optional[ID_TYPES]` | Optional distinct ID of the user performing the action | `None` **Examples:** @@ -2501,11 +2501,11 @@ Associate user behaviour before and after they e.g. register, login, or perform Name | Type | Description | Default ---- | ---- | ----------- | ------- -`previous_id` | ID_TYPES | The unique ID of the user before | *required* -`distinct_id` | str | The current unique id | *required* -`timestamp` | Optional[Union[datetime, str]] | Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | None -`uuid` | Optional[str] | Optional UUID for the event | None -`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup | None +`previous_id` | `ID_TYPES` | The unique ID of the user before | *required* +`distinct_id` | `str` | The current unique id | *required* +`timestamp` | `Optional[Union[datetime, str]]` | Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | `None` +`uuid` | `Optional[str]` | Optional UUID for the event | `None` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup | `None`
Details @@ -2534,8 +2534,8 @@ Capture exceptions that happen in your code. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`exception` | Optional[ExceptionArg] | The exception to capture. If not provided, the current exception is captured via `sys.exc_info()` | None -`**kwargs` | Unpack[OptionalCaptureArgs] | Optional capture arguments including distinct_id, properties, timestamp, uuid, groups, flags, send_feature_flags, and disable_geoip. Overriding reserved exception properties through `properties` is deprecated and will stop working in the next major version. | {} +`exception` | `Optional[ExceptionArg]` | The exception to capture. If not provided, the current exception is captured via `sys.exc_info()` | `None` +`**kwargs` | `Unpack[OptionalCaptureArgs]` | Optional capture arguments including distinct_id, properties, timestamp, uuid, groups, flags, send_feature_flags, and disable_geoip. Overriding reserved exception properties through `properties` is deprecated and will stop working in the next major version. | `{}`
Details @@ -2567,15 +2567,15 @@ Use feature flags to enable or disable features for users. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The feature flag key | *required* -`distinct_id` | ID_TYPES | The user's distinct ID | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | Groups mapping | None -`person_properties` | Optional[Dict[str, Any]] | Person properties | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties | None -`only_evaluate_locally` | bool | Whether to evaluate only locally | False -`send_feature_flag_events` | bool | Whether to send feature flag events | True -`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup | None -`device_id` | Optional[str] | Optional device ID override for experience-continuity flags | None +`key` | `str` | The feature flag key | *required* +`distinct_id` | `ID_TYPES` | The user's distinct ID | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | Groups mapping | `None` +`person_properties` | `Optional[Dict[str, Any]]` | Person properties | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties | `None` +`only_evaluate_locally` | `bool` | Whether to evaluate only locally | `False` +`send_feature_flag_events` | `bool` | Whether to send feature flag events | `True` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup | `None` +`device_id` | `Optional[str]` | Optional device ID override for experience-continuity flags | `None`
Details @@ -2606,15 +2606,15 @@ Get feature flag variant for users. Used with experiments. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The feature flag key | *required* -`distinct_id` | ID_TYPES | The user's distinct ID | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | Groups mapping from group type to group key | None -`person_properties` | Optional[Dict[str, Any]] | Person properties | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties in format { group_type_name: { group_properties } } | None -`only_evaluate_locally` | bool | Whether to evaluate only locally | False -`send_feature_flag_events` | bool | Whether to send feature flag events | True -`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup | None -`device_id` | Optional[str] | Optional device ID override for experience-continuity flags | None +`key` | `str` | The feature flag key | *required* +`distinct_id` | `ID_TYPES` | The user's distinct ID | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | Groups mapping from group type to group key | `None` +`person_properties` | `Optional[Dict[str, Any]]` | Person properties | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties in format { group_type_name: { group_properties } } | `None` +`only_evaluate_locally` | `bool` | Whether to evaluate only locally | `False` +`send_feature_flag_events` | `bool` | Whether to send feature flag events | `True` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup | `None` +`device_id` | `Optional[str]` | Optional device ID override for experience-continuity flags | `None`
Details @@ -2645,14 +2645,14 @@ Get all flags for a given user. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`distinct_id` | ID_TYPES | The user's distinct ID | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | Groups mapping | None -`person_properties` | Optional[Dict[str, Any]] | Person properties | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties | None -`only_evaluate_locally` | bool | Whether to evaluate only locally | False -`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup | None -`device_id` | Optional[str] | Optional device ID override for experience-continuity flags | None -`flag_keys_to_evaluate` | Optional[list[str]] | Optional list of flag keys to evaluate (evaluates all if None) | None +`distinct_id` | `ID_TYPES` | The user's distinct ID | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | Groups mapping | `None` +`person_properties` | `Optional[Dict[str, Any]]` | Person properties | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties | `None` +`only_evaluate_locally` | `bool` | Whether to evaluate only locally | `False` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup | `None` +`device_id` | `Optional[str]` | Optional device ID override for experience-continuity flags | `None` +`flag_keys_to_evaluate` | `Optional[list[str]]` | Optional list of flag keys to evaluate (evaluates all if None) | `None`
Details @@ -2688,15 +2688,15 @@ This method evaluates a feature flag and returns a FeatureFlagResult object cont Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The feature flag key. | *required* -`distinct_id` | ID_TYPES | The user's distinct ID. | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | Mapping of group type to group key. | None -`person_properties` | Optional[Dict[str, Any]] | Person properties to use for evaluation. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties keyed by group type. | None -`only_evaluate_locally` | bool | Whether to evaluate only locally. | False -`send_feature_flag_events` | bool | Whether to send a $feature_flag_called event. | True -`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup. | None -`device_id` | Optional[str] | Optional device ID override for experience-continuity flags. | None +`key` | `str` | The feature flag key. | *required* +`distinct_id` | `ID_TYPES` | The user's distinct ID. | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | Mapping of group type to group key. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | Person properties to use for evaluation. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties keyed by group type. | `None` +`only_evaluate_locally` | `bool` | Whether to evaluate only locally. | `False` +`send_feature_flag_events` | `bool` | Whether to send a $feature_flag_called event. | `True` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup. | `None` +`device_id` | `Optional[str]` | Optional device ID override for experience-continuity flags. | `None` Example: ```python @@ -2722,24 +2722,24 @@ Deprecated for new code. Prefer `evaluate_flags()` and Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The feature flag key. | *required* -`distinct_id` | ID_TYPES | The user's distinct ID. | *required* -`match_value` | Optional[FlagValue] | Optional flag value to use when selecting a payload. | None -`groups` | Optional[Mapping[str, Union[str, int]]] | Mapping of group type to group key. | None -`person_properties` | Optional[Dict[str, Any]] | Person properties to use for evaluation. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties keyed by group type. | None -`only_evaluate_locally` | bool | Whether to evaluate only locally. | False -`send_feature_flag_events` | bool | Whether to send a $feature_flag_called event. | True -`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup. | None -`device_id` | Optional[str] | Optional device ID override for experience-continuity flags. | None +`key` | `str` | The feature flag key. | *required* +`distinct_id` | `ID_TYPES` | The user's distinct ID. | *required* +`match_value` | `Optional[FlagValue]` | Optional flag value to use when selecting a payload. | `None` +`groups` | `Optional[Mapping[str, Union[str, int]]]` | Mapping of group type to group key. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | Person properties to use for evaluation. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties keyed by group type. | `None` +`only_evaluate_locally` | `bool` | Whether to evaluate only locally. | `False` +`send_feature_flag_events` | `bool` | Whether to send a $feature_flag_called event. | `True` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup. | `None` +`device_id` | `Optional[str]` | Optional device ID override for experience-continuity flags. | `None` **Returns:** Type | Description ---- | ----------- -Optional[object] | The payload associated with the matched feature flag value, or None. -Optional[object] | This function returns the payload only, not the FeatureFlagResult wrapper -Optional[object] | used internally to compute it. +`Optional[object]` | The payload associated with the matched feature flag value, or None. +`Optional[object]` | This function returns the payload only, not the FeatureFlagResult wrapper +`Optional[object]` | used internally to compute it. ### `posthog.get_remote_config_payload` @@ -2753,7 +2753,7 @@ Get the payload for a remote config feature flag. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The key of the feature flag | *required* +`key` | `str` | The key of the feature flag | *required* **Returns:** @@ -2780,20 +2780,20 @@ Get all feature flag values and payloads for a user. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`distinct_id` | ID_TYPES | The user's distinct ID. | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | Mapping of group type to group key. | None -`person_properties` | Optional[Dict[str, Any]] | Person properties to use for evaluation. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties keyed by group type. | None -`only_evaluate_locally` | bool | Whether to evaluate only locally. | False -`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup. | None -`device_id` | Optional[str] | Optional device ID override for experience-continuity flags. | None -`flag_keys_to_evaluate` | Optional[list[str]] | Optional list of flag keys to evaluate. Evaluates all flags when omitted. | None +`distinct_id` | `ID_TYPES` | The user's distinct ID. | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | Mapping of group type to group key. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | Person properties to use for evaluation. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties keyed by group type. | `None` +`only_evaluate_locally` | `bool` | Whether to evaluate only locally. | `False` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup. | `None` +`device_id` | `Optional[str]` | Optional device ID override for experience-continuity flags. | `None` +`flag_keys_to_evaluate` | `Optional[list[str]]` | Optional list of flag keys to evaluate. Evaluates all flags when omitted. | `None` **Returns:** Type | Description ---- | ----------- -FlagsAndPayloads | A dict with `featureFlags` and `featureFlagPayloads` entries. +`FlagsAndPayloads` | A dict with `featureFlags` and `featureFlagPayloads` entries. ### `posthog.evaluate_flags` @@ -2814,14 +2814,14 @@ a single `/flags` request per incoming request. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`distinct_id` | Optional[ID_TYPES] | The user's distinct ID. If `None`, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot. | None -`groups` | Optional[Mapping[str, Union[str, int]]] | Mapping of group type to group key. | None -`person_properties` | Optional[Dict[str, Any]] | Person properties to use for evaluation. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties keyed by group type. | None -`only_evaluate_locally` | bool | If `True`, never fall back to remote evaluation and omit flags that cannot be evaluated locally. The snapshot's `unresolved_flags` lists them with a reason. | False -`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup. | None -`flag_keys` | Optional[list[str]] | Optional list that scopes local evaluation, the underlying `/flags` request, and the returned snapshot. When omitted or `None`, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per `evaluate_flags` call unless `only_evaluate_locally` is `True`. If the server also does not know the key, it is omitted from the snapshot. | None -`device_id` | Optional[str] | Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes. | None +`distinct_id` | `Optional[ID_TYPES]` | The user's distinct ID. If `None`, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot. | `None` +`groups` | `Optional[Mapping[str, Union[str, int]]]` | Mapping of group type to group key. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | Person properties to use for evaluation. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties keyed by group type. | `None` +`only_evaluate_locally` | `bool` | If `True`, never fall back to remote evaluation and omit flags that cannot be evaluated locally. The snapshot's `unresolved_flags` lists them with a reason. | `False` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup. | `None` +`flag_keys` | `Optional[list[str]]` | Optional list that scopes local evaluation, the underlying `/flags` request, and the returned snapshot. When omitted or `None`, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per `evaluate_flags` call unless `only_evaluate_locally` is `True`. If the server also does not know the key, it is omitted from the snapshot. | `None` +`device_id` | `Optional[str]` | Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes. | `None` **Examples:** @@ -2956,7 +2956,7 @@ rather than blocking the worker that invoked the callback. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`timeout_seconds` | Optional[float] | Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass `None` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently, waiting up to this many seconds for an in-flight flush before starting a fresh drain budget. A lock wait timeout leaves the in-flight flush to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. The last span request is not cut short and is bounded by the client's `timeout` in seconds, so this is not a strict wall-clock limit for the whole call. With `None`, a retriable span failure is left for a later flush rather than retried within this call. | 10 +`timeout_seconds` | `Optional[float]` | Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass `None` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently, waiting up to this many seconds for an in-flight flush before starting a fresh drain budget. A lock wait timeout leaves the in-flight flush to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. The last span request is not cut short and is bounded by the client's `timeout` in seconds, so this is not a strict wall-clock limit for the whole call. With `None`, a retriable span failure is left for a later flush rather than retried within this call. | `10` **Examples:** @@ -3004,12 +3004,12 @@ a usable handle, even when tracing is off, so calling code never branches. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`name` | str | A low-cardinality operation name, e.g. `GET /users/:id`. Variable values belong in attributes, not the name. | *required* -`kind` | Optional[str] | `internal` (default), `server`, `client`, `producer` or `consumer`. | None -`attributes` | Optional[Mapping[str, Any]] | Initial attributes. | None -`parent` | Union[Span, str, None] | A span handle, or an inbound W3C `traceparent` header value to continue a remote trace. Defaults to the active span. | None -`tracestate` | Optional[str] | The inbound `tracestate` header accompanying a `traceparent` string `parent`; preserved and propagated. | None -`start_time` | Union[datetime, float, None] | A `datetime` or epoch seconds, to backdate the span. | None +`name` | `str` | A low-cardinality operation name, e.g. `GET /users/:id`. Variable values belong in attributes, not the name. | *required* +`kind` | `Optional[str]` | `internal` (default), `server`, `client`, `producer` or `consumer`. | `None` +`attributes` | `Optional[Mapping[str, Any]]` | Initial attributes. | `None` +`parent` | `Union[Span, str, None]` | A span handle, or an inbound W3C `traceparent` header value to continue a remote trace. Defaults to the active span. | `None` +`tracestate` | `Optional[str]` | The inbound `tracestate` header accompanying a `traceparent` string `parent`; preserved and propagated. | `None` +`start_time` | `Union[datetime, float, None]` | A `datetime` or epoch seconds, to backdate the span. | `None` **Examples:** @@ -3095,15 +3095,15 @@ Most applications should either instantiate `Posthog` directly or set Type | Description ---- | ----------- -Client | The global `Client` instance. If both `api_key` and -Client | `project_api_key` are missing or blank, the client is disabled and -Client | module-level calls become no-ops. +`Client` | The global `Client` instance. If both `api_key` and +`Client` | `project_api_key` are missing or blank, the client is disabled and +`Client` | module-level calls become no-ops. ## Types ### `OptionalCaptureArgs` -Bases: TypedDict +Bases: `TypedDict` Optional arguments for the capture method. @@ -3124,14 +3124,14 @@ Name | Type | Description | Default Name | Type | Description ---- | ---- | ----------- -`disable_geoip` | NotRequired[Optional[bool]] | -`distinct_id` | NotRequired[Optional[ID_TYPES]] | -`flags` | NotRequired[Optional[FeatureFlagEvaluations]] | -`groups` | NotRequired[Optional[Dict[str, str]]] | -`properties` | NotRequired[Optional[Dict[str, Any]]] | -`send_feature_flags` | NotRequired[Optional[Union[bool, SendFeatureFlagsOptions]]] | -`timestamp` | NotRequired[Optional[Union[datetime, str]]] | -`uuid` | NotRequired[Optional[Union[str, UUID]]] | +`disable_geoip` | `NotRequired[Optional[bool]]` | +`distinct_id` | `NotRequired[Optional[ID_TYPES]]` | +`flags` | `NotRequired[Optional[FeatureFlagEvaluations]]` | +`groups` | `NotRequired[Optional[Dict[str, str]]]` | +`properties` | `NotRequired[Optional[Dict[str, Any]]]` | +`send_feature_flags` | `NotRequired[Optional[Union[bool, SendFeatureFlagsOptions]]]` | +`timestamp` | `NotRequired[Optional[Union[datetime, str]]]` | +`uuid` | `NotRequired[Optional[Union[str, UUID]]]` | #### `posthog.args.OptionalCaptureArgs.disable_geoip` @@ -3219,8 +3219,8 @@ Name | Description Name | Type | Description ---- | ---- | ----------- -`keys` | List[str] | Return the flag keys that are part of this evaluation. -`unresolved_flags` | Mapping[str, UnresolvedFlagReason] | Return a read-only mapping of flag key to the reason local evaluation +`keys` | `List[str]` | Return the flag keys that are part of this evaluation. +`unresolved_flags` | `Mapping[str, UnresolvedFlagReason]` | Return a read-only mapping of flag key to the reason local evaluation Internal — instances are created by the SDK via `Client.evaluate_flags()`. @@ -3262,9 +3262,9 @@ first access per (distinct_id, flag, value) tuple, deduped via the SDK's cache. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The feature flag key. | *required* -`default_value` | bool | Returned when the flag has no value in this evaluation — the key was not part of the evaluated set, or the evaluation itself came back empty (failed `/flags` request, quota limit, or no resolvable `distinct_id`). Defaults to `False`. | False -`send_feature_flag_events` | bool | Whether this read sends `$feature_flag_called`. When `False`, no event is sent and the dedupe cache is left alone, but the key still counts as accessed for `only_accessed`. | True +`key` | `str` | The feature flag key. | *required* +`default_value` | `bool` | Returned when the flag has no value in this evaluation — the key was not part of the evaluated set, or the evaluation itself came back empty (failed `/flags` request, quota limit, or no resolvable `distinct_id`). Defaults to `False`. | `False` +`send_feature_flag_events` | `bool` | Whether this read sends `$feature_flag_called`. When `False`, no event is sent and the dedupe cache is left alone, but the key still counts as accessed for `only_accessed`. | `True` A flag that has a value always wins over `default_value`, including a disabled flag, which returns `False` even when `default_value=True`. @@ -3328,11 +3328,11 @@ The result of calling a feature flag which includes the flag result, variant, an Name | Type | Description ---- | ---- | ----------- -`key` | str | The unique identifier of the feature flag. -`enabled` | bool | Whether the feature flag is enabled for the current context. -`variant` | Optional[str] | The variant value if the flag is enabled and has variants, None otherwise. -`payload` | Optional[Any] | Additional data associated with the feature flag, if any. -`reason` | Optional[str] | A description of why the flag was enabled or disabled, if available. +`key` | `str` | The unique identifier of the feature flag. +`enabled` | `bool` | Whether the feature flag is enabled for the current context. +`variant` | `Optional[str]` | The variant value if the flag is enabled and has variants, None otherwise. +`payload` | `Optional[Any]` | Additional data associated with the feature flag, if any. +`reason` | `Optional[str]` | A description of why the flag was enabled or disabled, if available. **Functions:** @@ -3360,14 +3360,14 @@ Create a FeatureFlagResult from a FeatureFlag object. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`details` | Union[FeatureFlag, None] | The FeatureFlag object to convert. | *required* -`override_match_value` | Optional[FlagValue] | If provided, this value will be used to populate the enabled and variant fields instead of the values from the FeatureFlag. | None +`details` | `Union[FeatureFlag, None]` | The FeatureFlag object to convert. | *required* +`override_match_value` | `Optional[FlagValue]` | If provided, this value will be used to populate the enabled and variant fields instead of the values from the FeatureFlag. | `None` **Returns:** Type | Description ---- | ----------- -FeatureFlagResult \| None | FeatureFlagResult | None: A new FeatureFlagResult instance, or None if details is None. +`FeatureFlagResult \| None` | FeatureFlagResult | None: A new FeatureFlagResult instance, or None if details is None. #### `posthog.types.FeatureFlagResult.from_value_and_payload` @@ -3381,15 +3381,15 @@ Creates a FeatureFlagResult from a flag value and payload. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The unique identifier of the feature flag. | *required* -`value` | Union[FlagValue, None] | The value of the flag (string variant or boolean). | *required* -`payload` | Any | Additional data associated with the feature flag. | *required* +`key` | `str` | The unique identifier of the feature flag. | *required* +`value` | `Union[FlagValue, None]` | The value of the flag (string variant or boolean). | *required* +`payload` | `Any` | Additional data associated with the feature flag. | *required* **Returns:** Type | Description ---- | ----------- -Union[FeatureFlagResult, None] | Union[FeatureFlagResult, None]: A new FeatureFlagResult instance, or None if value is None. +`Union[FeatureFlagResult, None]` | Union[FeatureFlagResult, None]: A new FeatureFlagResult instance, or None if value is None. #### `posthog.types.FeatureFlagResult.get_value` @@ -3404,7 +3404,7 @@ This is the value we report as `$feature_flag_response` in the `$feature_flag_ca Name | Type | Description ---- | ---- | ----------- -`FlagValue` | FlagValue | Either a string variant or boolean value representing the flag's state. +`FlagValue` | `FlagValue` | Either a string variant or boolean value representing the flag's state. #### `posthog.types.FeatureFlagResult.key` @@ -3445,11 +3445,11 @@ responses before `/flags?v=4` carried it, which the legacy getters return. Name | Type | Description ---- | ---- | ----------- -`key` | str | Feature flag key. -`enabled` | bool | Whether the flag is enabled for the evaluated user or group. -`variant` | Optional[str] | Variant key for multivariate flags, otherwise `None`. -`reason` | Optional[FlagReason] | Optional reason metadata explaining the result. -`metadata` | Union[FlagMetadata, LegacyFlagMetadata] | Payload and other metadata returned by the API. +`key` | `str` | Feature flag key. +`enabled` | `bool` | Whether the flag is enabled for the evaluated user or group. +`variant` | `Optional[str]` | Variant key for multivariate flags, otherwise `None`. +`reason` | `Optional[FlagReason]` | Optional reason metadata explaining the result. +`metadata` | `Union[FlagMetadata, LegacyFlagMetadata]` | Payload and other metadata returned by the API. **Functions:** @@ -3519,7 +3519,7 @@ variant: Optional[str] ### `FlagsAndPayloads` -Bases: TypedDict +Bases: `TypedDict` Feature flag values and payloads keyed by feature flag key. @@ -3527,8 +3527,8 @@ Feature flag values and payloads keyed by feature flag key. Name | Type | Description ---- | ---- | ----------- -`featureFlags` | Optional[dict[str, FlagValue]] | Each flag's value, keyed by flag key. -`featureFlagPayloads` | Optional[dict[str, Any]] | Each flag's payload, keyed by flag key. +`featureFlags` | `Optional[dict[str, FlagValue]]` | Each flag's value, keyed by flag key. +`featureFlagPayloads` | `Optional[dict[str, Any]]` | Each flag's payload, keyed by flag key. #### `posthog.types.FlagsAndPayloads.featureFlagPayloads` @@ -3544,7 +3544,7 @@ featureFlags: Optional[dict[str, FlagValue]] ### `UnresolvedFlagReason` -Bases: str, Enum +Bases: `str`, `Enum` Why local evaluation left a flag without a value. @@ -3607,7 +3607,7 @@ UNSUPPORTED_DEFINITION = 'unsupported_definition' InconclusiveMatchError(*args, reason: Optional[UnresolvedFlagReason] = None) ``` -Bases: Exception +Bases: `Exception` Raised when local evaluation can't determine a flag's value. `reason` says why, as reported by `FeatureFlagEvaluations.unresolved_flags`. @@ -3616,7 +3616,7 @@ why, as reported by `FeatureFlagEvaluations.unresolved_flags`. Name | Type | Description ---- | ---- | ----------- -`reason` | UnresolvedFlagReason | Why local evaluation couldn't resolve the flag. +`reason` | `UnresolvedFlagReason` | Why local evaluation couldn't resolve the flag. #### `posthog.feature_flags.InconclusiveMatchError.reason` @@ -3630,7 +3630,7 @@ reason: UnresolvedFlagReason = UnresolvedFlagReason.UNSUPPORTED_DEFINITION RequiresServerEvaluation(*args, reason: Optional[UnresolvedFlagReason] = None) ``` -Bases: Exception +Bases: `Exception` Raised when feature flag evaluation requires server-side data that is not available locally (e.g., static cohorts). @@ -3642,7 +3642,7 @@ InconclusiveMatchError which allows trying other conditions. Name | Type | Description ---- | ---- | ----------- -`reason` | UnresolvedFlagReason | Why the flag needs server-side evaluation. +`reason` | `UnresolvedFlagReason` | Why the flag needs server-side evaluation. #### `posthog.feature_flags.RequiresServerEvaluation.reason` @@ -3834,7 +3834,7 @@ span.update_name(f"{request.method} {route.pattern}") Emails(secret_key: Optional[str], *, host: Optional[str] = None, timeout: float = 15, max_retries: int = 2, disabled: bool = False, session: Optional[requests.Session] = None) -> None ``` -Bases: _EmailsBase +Bases: `_EmailsBase` Sends transactional email from PostHog email templates. @@ -3892,20 +3892,20 @@ Read what happened to an email that `send` queued. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`message_id` | Union[str, UUID] | The `id` of the `SendEmailResult` that `send` returned. | *required* +`message_id` | `Union[str, UUID]` | The `id` of the `SendEmailResult` that `send` returned. | *required* **Returns:** Type | Description ---- | ----------- -EmailStatus | The email's latest status, for 30 days after the send. +`EmailStatus` | The email's latest status, for 30 days after the send. **Raises:** Type | Description ---- | ----------- -EmailError | PostHog did not answer with a status. `code` is `not_found` for an unknown ID, an email older than 30 days, or for a few seconds after the send. -ValueError | `message_id` is not a message ID. +`EmailError` | PostHog did not answer with a status. `code` is `not_found` for an unknown ID, an email older than 30 days, or for a few seconds after the send. +`ValueError` | `message_id` is not a message ID. **Examples:** @@ -3946,26 +3946,26 @@ Queue one email from a transactional email template. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`to` | str | One recipient email address. | *required* -`template_id` | Union[str, UUID] | The UUID of a template made transactional in PostHog. | *required* -`template_data` | Optional[Mapping[str, Any]] | Values for the template's `{{ variables.* }}` placeholders. Values are encoded the way capture encodes properties. | None -`distinct_id` | Optional[str] | The recipient's distinct ID in your product. The email's events go to this person. Without it, they go to a person whose distinct ID is the email address. | None -`idempotency_key` | Optional[str] | 1 to 255 visible ASCII characters. A send with a key that PostHog has already accepted queues nothing and returns the first send's result. Without it, the client makes a key that covers its own retries. Pass a key derived from what the email is for, such as `welcome:user_123`, to cover retries outside this call, too. | None -`max_retries` | Optional[int] | Overrides the client's maximum number of retries for this send. `0` turns retries off. | None +`to` | `str` | One recipient email address. | *required* +`template_id` | `Union[str, UUID]` | The UUID of a template made transactional in PostHog. | *required* +`template_data` | `Optional[Mapping[str, Any]]` | Values for the template's `{{ variables.* }}` placeholders. Values are encoded the way capture encodes properties. | `None` +`distinct_id` | `Optional[str]` | The recipient's distinct ID in your product. The email's events go to this person. Without it, they go to a person whose distinct ID is the email address. | `None` +`idempotency_key` | `Optional[str]` | 1 to 255 visible ASCII characters. A send with a key that PostHog has already accepted queues nothing and returns the first send's result. Without it, the client makes a key that covers its own retries. Pass a key derived from what the email is for, such as `welcome:user_123`, to cover retries outside this call, too. | `None` +`max_retries` | `Optional[int]` | Overrides the client's maximum number of retries for this send. `0` turns retries off. | `None` **Returns:** Type | Description ---- | ----------- -SendEmailResult | The queued email. A result means PostHog queued the email, not that -SendEmailResult | it was delivered. +`SendEmailResult` | The queued email. A result means PostHog queued the email, not that +`SendEmailResult` | it was delivered. **Raises:** Type | Description ---- | ----------- -EmailError | PostHog did not queue the email, or the client could not tell whether it did. -(TypeError, ValueError) | An argument has the wrong type or format. +`EmailError` | PostHog did not queue the email, or the client could not tell whether it did. +`(TypeError, ValueError)` | An argument has the wrong type or format. **Examples:** @@ -3991,7 +3991,7 @@ timeout = timeout EmailError(*, name: str, code: str, message: str, status_code: Optional[int] = None, why: Optional[str] = None, fix: Optional[str] = None, docs_url: Optional[str] = None, field: Optional[str] = None, retry_after: Optional[float] = None) -> None ``` -Bases: Exception +Bases: `Exception` PostHog did not queue the email, or the client could not tell whether it did. @@ -4079,9 +4079,9 @@ The queued email. `id` is its message ID. Name | Type | Description ---- | ---- | ----------- -`id` | str | The email's message ID. -`replayed` | bool | `True` when the response says PostHog replayed an earlier send for the same `idempotency_key` instead of sending again. -`warnings` | tuple[EmailWarning, ...] | Warnings PostHog returned about the request. +`id` | `str` | The email's message ID. +`replayed` | `bool` | `True` when the response says PostHog replayed an earlier send for the same `idempotency_key` instead of sending again. +`warnings` | `tuple[EmailWarning, ...]` | Warnings PostHog returned about the request. #### `posthog.emails.SendEmailResult.id` diff --git a/references/posthog-python-8.md b/references/posthog-python-8.md index 5c887acc7..325838400 100644 --- a/references/posthog-python-8.md +++ b/references/posthog-python-8.md @@ -139,12 +139,12 @@ code never branches. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`name` | str | A low-cardinality operation name, e.g. `GET /users/:id`. Variable values belong in attributes, not the name. | *required* -`kind` | Optional[str] | `internal` (default), `server`, `client`, `producer` or `consumer`. | None -`attributes` | Optional[Mapping[str, Any]] | Initial attributes. | None -`parent` | Union[Span, str, None] | A span handle, or an inbound W3C `traceparent` header value to continue a remote trace. Defaults to the active span. A forked child starts with no active span; pass the parent span to continue a trace across a fork. | None -`tracestate` | Optional[str] | The inbound `tracestate` header accompanying a `traceparent` string `parent`; preserved and propagated. | None -`start_time` | Union[datetime, float, None] | A `datetime` or epoch seconds, to backdate the span. | None +`name` | `str` | A low-cardinality operation name, e.g. `GET /users/:id`. Variable values belong in attributes, not the name. | *required* +`kind` | `Optional[str]` | `internal` (default), `server`, `client`, `producer` or `consumer`. | `None` +`attributes` | `Optional[Mapping[str, Any]]` | Initial attributes. | `None` +`parent` | `Union[Span, str, None]` | A span handle, or an inbound W3C `traceparent` header value to continue a remote trace. Defaults to the active span. A forked child starts with no active span; pass the parent span to continue a trace across a fork. | `None` +`tracestate` | `Optional[str]` | The inbound `tracestate` header accompanying a `traceparent` string `parent`; preserved and propagated. | `None` +`start_time` | `Union[datetime, float, None]` | A `datetime` or epoch seconds, to backdate the span. | `None` **Examples:** @@ -192,12 +192,12 @@ Create an alias between two distinct IDs. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`previous_id` | ID_TYPES | The previous distinct ID. Required - the call is dropped with a warning if it is missing or empty. | *required* -`distinct_id` | Optional[str] | The new distinct ID to alias to. Required - the call is dropped with a warning if it is missing or empty. The context distinct ID is never used here. | *required* -`timestamp` | Optional[Union[datetime, str]] | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | None -`uuid` | Optional[str] | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | None -`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this event. | None -`options` | Optional[Dict[str, Any]] | Capture options for this event, sent as given. | None +`previous_id` | `ID_TYPES` | The previous distinct ID. Required - the call is dropped with a warning if it is missing or empty. | *required* +`distinct_id` | `Optional[str]` | The new distinct ID to alias to. Required - the call is dropped with a warning if it is missing or empty. The context distinct ID is never used here. | *required* +`timestamp` | `Optional[Union[datetime, str]]` | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | `None` +`uuid` | `Optional[str]` | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | `None` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this event. | `None` +`options` | `Optional[Dict[str, Any]]` | Capture options for this event, sent as given. | `None` **Examples:** @@ -221,7 +221,7 @@ Captures an event manually. [Learn about capture best practices](https://posthog Name | Type | Description | Default ---- | ---- | ----------- | ------- -`event` | str | The event name to capture. | *required* +`event` | `str` | The event name to capture. | *required* `distinct_id` | | The distinct ID of the user. | `properties` | | A dictionary of properties to include with the event. | `timestamp` | | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | @@ -307,8 +307,8 @@ unless the event, a context tag or `super_properties` sets either one. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`exception` | Optional[ExceptionArg] | The exception to capture. | *required* -`level` | Optional[str] | The `$exception_level`, such as `"warning"` or `"fatal"`. Defaults to `"error"`. An unknown value counts as unset. | None +`exception` | `Optional[ExceptionArg]` | The exception to capture. | *required* +`level` | `Optional[str]` | The `$exception_level`, such as `"warning"` or `"fatal"`. Defaults to `"error"`. An unknown value counts as unset. | `None` `distinct_id` | | The distinct ID of the user. | `properties` | | A dictionary of additional properties. Reserved exception properties, such as `$exception_list` and `$exception_level`, are ignored. | `flags` | | A `FeatureFlagEvaluations` snapshot from `evaluate_flags()`. Attaches those exact flag values to the captured `$exception` event. | @@ -378,7 +378,7 @@ deferred rather than blocking the worker that invoked the callback. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`timeout_seconds` | Optional[float] | Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass `None` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently. The span exporter waits up to this many seconds for an in-flight flush, then uses a fresh drain budget once it acquires the lock. If the lock wait times out, the in-flight flush is left to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. Each span request is bounded by the client's `timeout` in seconds, and the last request is not cut short. This is therefore not a strict wall-clock limit for the whole call. With `None`, a retriable span failure is left for a later flush rather than retried within this call. | 10 +`timeout_seconds` | `Optional[float]` | Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass `None` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently. The span exporter waits up to this many seconds for an in-flight flush, then uses a fresh drain budget once it acquires the lock. If the lock wait times out, the in-flight flush is left to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. Each span request is bounded by the client's `timeout` in seconds, and the last request is not cut short. This is therefore not a strict wall-clock limit for the whole call. With `None`, a retriable span failure is left for a later flush rather than retried within this call. | `10` **Examples:** @@ -402,15 +402,15 @@ Get multivariate feature flag value for a user. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The feature flag key. | *required* -`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None -`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None -`only_evaluate_locally` | bool | Whether to only evaluate locally. | False -`send_feature_flag_events` | bool | Whether to send feature flag events. | True -`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None -`device_id` | Optional[str] | The device ID for this request. | None +`key` | `str` | The feature flag key. | *required* +`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` +`only_evaluate_locally` | `bool` | Whether to only evaluate locally. | `False` +`send_feature_flag_events` | `bool` | Whether to send feature flag events. | `True` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` +`device_id` | `Optional[str]` | The device ID for this request. | `None` **Examples:** @@ -436,24 +436,24 @@ Get the payload for a feature flag. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The feature flag key. | *required* -`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* -`match_value` | Optional[FlagValue] | The specific flag value to get payload for. | None -`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None -`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None -`only_evaluate_locally` | bool | Whether to only evaluate locally. | False -`send_feature_flag_events` | bool | Deprecated. Use get_feature_flag() instead if you need events. | False -`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None -`device_id` | Optional[str] | The device ID for this request. | None +`key` | `str` | The feature flag key. | *required* +`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* +`match_value` | `Optional[FlagValue]` | The specific flag value to get payload for. | `None` +`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` +`only_evaluate_locally` | `bool` | Whether to only evaluate locally. | `False` +`send_feature_flag_events` | `bool` | Deprecated. Use get_feature_flag() instead if you need events. | `False` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` +`device_id` | `Optional[str]` | The device ID for this request. | `None` **Returns:** Type | Description ---- | ----------- -Optional[object] | The payload associated with the matched feature flag value, or None. -Optional[object] | This method returns the payload only, not the FeatureFlagResult wrapper -Optional[object] | used internally to compute it. +`Optional[object]` | The payload associated with the matched feature flag value, or None. +`Optional[object]` | This method returns the payload only, not the FeatureFlagResult wrapper +`Optional[object]` | used internally to compute it. **Examples:** @@ -492,21 +492,21 @@ if flag_result and flag_result.get_value() == 'variant-key': Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The feature flag key. | *required* -`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None -`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None -`only_evaluate_locally` | bool | Whether to only evaluate locally. | False -`send_feature_flag_events` | bool | Whether to send feature flag events. | True -`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None -`device_id` | Optional[str] | The device ID for this request. | None +`key` | `str` | The feature flag key. | *required* +`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` +`only_evaluate_locally` | `bool` | Whether to only evaluate locally. | `False` +`send_feature_flag_events` | `bool` | Whether to send feature flag events. | `True` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` +`device_id` | `Optional[str]` | The device ID for this request. | `None` **Returns:** Type | Description ---- | ----------- -Optional[FeatureFlagResult] | Optional[FeatureFlagResult]: The feature flag result or None if disabled/not found. +`Optional[FeatureFlagResult]` | Optional[FeatureFlagResult]: The feature flag result or None if disabled/not found. ## Get feature flags @@ -522,13 +522,13 @@ Get feature flag variants for a user. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None -`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None -`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None -`flag_keys_to_evaluate` | Optional[list[str]] | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | None -`device_id` | Optional[str] | The device ID for this request. | None +`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` +`flag_keys_to_evaluate` | `Optional[list[str]]` | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | `None` +`device_id` | `Optional[str]` | The device ID for this request. | `None` ### `Client.get_all_flags` @@ -542,14 +542,14 @@ Get all feature flags for a user. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None -`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None -`only_evaluate_locally` | bool | Whether to only evaluate locally. | False -`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None -`flag_keys_to_evaluate` | Optional[list[str]] | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | None -`device_id` | Optional[str] | The device ID for this request. | None +`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` +`only_evaluate_locally` | `bool` | Whether to only evaluate locally. | `False` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` +`flag_keys_to_evaluate` | `Optional[list[str]]` | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | `None` +`device_id` | `Optional[str]` | The device ID for this request. | `None` **Examples:** @@ -571,13 +571,13 @@ Get feature flags and payloads for a user. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None -`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None -`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None -`flag_keys_to_evaluate` | Optional[list[str]] | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | None -`device_id` | Optional[str] | The device ID for this request. | None +`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` +`flag_keys_to_evaluate` | `Optional[list[str]]` | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | `None` +`device_id` | `Optional[str]` | The device ID for this request. | `None` **Examples:** @@ -597,14 +597,14 @@ Get all feature flags and their payloads for a user. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None -`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None -`only_evaluate_locally` | bool | Whether to only evaluate locally. | False -`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None -`flag_keys_to_evaluate` | Optional[list[str]] | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | None -`device_id` | Optional[str] | The device ID for this request. | None +`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` +`only_evaluate_locally` | `bool` | Whether to only evaluate locally. | `False` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` +`flag_keys_to_evaluate` | `Optional[list[str]]` | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | `None` +`device_id` | `Optional[str]` | The device ID for this request. | `None` **Examples:** @@ -637,20 +637,20 @@ and reason `"Evaluated locally"`. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`distinct_id` | Optional[ID_TYPES] | The user's distinct ID. If `None`, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot. | None -`groups` | Optional[Mapping[str, Union[str, int]]] | Mapping of group type to group key. | None -`person_properties` | Optional[Dict[str, Any]] | Person properties to use for evaluation. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties keyed by group type. | None -`only_evaluate_locally` | bool | If True, never fall back to remote evaluation — flags that can't be evaluated locally are omitted from the snapshot and listed with a reason in its `unresolved_flags`. | False -`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup. | None -`flag_keys` | Optional[List[str]] | Optional list that scopes local evaluation, the underlying `/flags` request, and the returned snapshot. When omitted or `None`, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per `evaluate_flags` call unless `only_evaluate_locally` is True. If the server also does not know the key, it is omitted from the snapshot. | None -`device_id` | Optional[str] | Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes. | None +`distinct_id` | `Optional[ID_TYPES]` | The user's distinct ID. If `None`, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot. | `None` +`groups` | `Optional[Mapping[str, Union[str, int]]]` | Mapping of group type to group key. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | Person properties to use for evaluation. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties keyed by group type. | `None` +`only_evaluate_locally` | `bool` | If True, never fall back to remote evaluation — flags that can't be evaluated locally are omitted from the snapshot and listed with a reason in its `unresolved_flags`. | `False` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup. | `None` +`flag_keys` | `Optional[List[str]]` | Optional list that scopes local evaluation, the underlying `/flags` request, and the returned snapshot. When omitted or `None`, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per `evaluate_flags` call unless `only_evaluate_locally` is True. If the server also does not know the key, it is omitted from the snapshot. | `None` +`device_id` | `Optional[str]` | Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes. | `None` **Returns:** Type | Description ---- | ----------- -FeatureFlagEvaluations | A `FeatureFlagEvaluations` snapshot. +`FeatureFlagEvaluations` | A `FeatureFlagEvaluations` snapshot. **Examples:** @@ -684,14 +684,14 @@ Identify a group and set its properties. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`group_type` | str | The type of group (e.g., 'company', 'team'). Required - the call is dropped with a warning if it is missing or empty. | *required* -`group_key` | str | The unique identifier for the group. Required - the call is dropped with a warning if it is missing or empty. | *required* -`properties` | Optional[Dict[str, Any]] | A dictionary of properties to set on the group. | None -`timestamp` | Optional[Union[datetime, str]] | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | None -`uuid` | Optional[Union[str, UUID]] | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | None -`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this event. | None -`distinct_id` | Optional[ID_TYPES] | The distinct ID of the user performing the action. Falls back to the context distinct ID, then to `$_`. | None -`options` | Optional[Dict[str, Any]] | Capture options for this event, sent as given. | None +`group_type` | `str` | The type of group (e.g., 'company', 'team'). Required - the call is dropped with a warning if it is missing or empty. | *required* +`group_key` | `str` | The unique identifier for the group. Required - the call is dropped with a warning if it is missing or empty. | *required* +`properties` | `Optional[Dict[str, Any]]` | A dictionary of properties to set on the group. | `None` +`timestamp` | `Optional[Union[datetime, str]]` | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | `None` +`uuid` | `Optional[Union[str, UUID]]` | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | `None` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this event. | `None` +`distinct_id` | `Optional[ID_TYPES]` | The distinct ID of the user performing the action. Falls back to the context distinct ID, then to `$_`. | `None` +`options` | `Optional[Dict[str, Any]]` | Capture options for this event, sent as given. | `None` **Examples:** @@ -773,15 +773,15 @@ Check if a feature flag is enabled for a user. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The feature flag key. | *required* -`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None -`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None -`only_evaluate_locally` | bool | Whether to only evaluate locally. | False -`send_feature_flag_events` | bool | Whether to send feature flag events. | True -`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None -`device_id` | Optional[str] | The device ID for this request. | None +`key` | `str` | The feature flag key. | *required* +`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` +`only_evaluate_locally` | `bool` | Whether to only evaluate locally. | `False` +`send_feature_flag_events` | `bool` | Whether to send feature flag events. | `True` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` +`device_id` | `Optional[str]` | The device ID for this request. | `None` **Examples:** @@ -807,58 +807,58 @@ Initialize a new PostHog client instance. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`project_api_key` | str | PostHog project API key/token. | *required* -`host` | | PostHog host. Defaults to the US ingestion endpoint when not set. App hosts such as `https://us.posthog.com` are mapped to the corresponding ingestion host. | None -`debug` | | Enable verbose SDK logging and re-raise errors from public API methods. | False -`max_queue_size` | | Maximum number of analytics events buffered before upload. AI events use `capture_ai_max_queue_size`. | 10000 -`send` | | If False, queueing succeeds but events are not sent. | True -`on_error` | | Optional callback `(error, batch)` invoked when an upload fails: by background consumers, or on the calling thread in `sync_mode`. Capture failures arrive as `CaptureError`. Without it, each failed batch logs one aggregate line. A capture that fails inside the callback logs that line instead of calling it again. Keep it short and non-blocking. Calling lifecycle methods directly is safe and deferred, but do not start another thread or task that calls `flush()`, `join()`, or `shutdown()` and then wait for it from the callback. | None -`flush_at` | | Number of queued events that triggers a batch upload. | 100 -`flush_interval` | | Maximum seconds a background consumer waits before flushing a partial batch. | 5.0 -`max_retries` | | Number of upload retries. Values below 0 are treated as 0. | 3 -`sync_mode` | | If True, send each event synchronously instead of using background worker threads. This blocks the calling thread; in asyncio applications such as FastAPI, use `AsyncPosthog` instead. | False -`timeout` | float | HTTP request timeout in seconds for event uploads. | 15 -`thread` | | Number of background consumer threads. | 1 -`poll_interval` | | Seconds between local feature flag definition refreshes. | 30 -`secret_key` | | A Personal API Key or Project Secret API Key, used to authenticate local feature flag evaluation, remote config payloads, and decrypted flag payloads. Example::
posthog.Client(project_api_key, secret_key="phx_...") | None -`personal_api_key` | | Deprecated alias for `secret_key`. Still honored for backwards compatibility; prefer `secret_key`, which also accepts a Project Secret API Key. | None -`disabled` | | If True, disable captures and API requests. Useful in tests. | False -`disable_geoip` | | Whether to disable server-side GeoIP enrichment. Defaults to True. Events get `$geoip_disable` only when the event, context tags and `super_properties` leave it unset. | True -`is_server` | | Whether events are emitted from a server-side runtime. Defaults to True; set to False when using the SDK as a client/CLI so the device OS is attributed to the person normally. Events get `$is_server` only when the event, context tags and `super_properties` leave it unset. | True -`historical_migration` | | Mark events as historical migration imports. | False -`feature_flags_request_timeout_seconds` | float | Timeout in seconds for feature flag and remote config requests. | 3 -`feature_flags_request_max_retries` | | Number of retries for feature flag requests after network, transport, or timeout failures. Defaults to 1. Set to 0 to disable retries. | 1 -`app_host` | Optional[str] | Your PostHog app host, such as `https://posthog.example.com`. `emails` sends there. Needed only when `host` is not PostHog Cloud, for example a reverse proxy, which must not receive the secret key. | None -`email_request_timeout_seconds` | float | Timeout in seconds for each email send request. Defaults to 15. | 15 -`email_request_max_retries` | int | Number of retries for an email send after a network error, a rate limit, or an unavailable service. Defaults to 2. Set to 0 when a job runner already retries the send. | 2 -`super_properties` | | Properties for every captured event. They fill only keys the event leaves unset, before `before_send` runs. `$set`, `$set_once`, `$groups` and `$group_set` fill one level deep. An event's own properties and context tags override them, and `before_send` can change or remove them. | None -`super_options` | | Capture options for every captured event, such as `{"cookieless_mode": True}`. They fill only options the event leaves unset, before `before_send` runs. An event's own `options` and context options override them, and `before_send` can change them. They also win over an event's legacy property for the same key, such as `$cookieless_mode`, so pass per-event overrides of that key as `options`. | None -`enable_exception_autocapture` | | Automatically capture uncaught exceptions. | False -`log_captured_exceptions` | | Also log exceptions captured by error tracking. | False -`project_root` | | Root path used to determine in-app stack frames for captured exceptions. Defaults to the current working directory. | None -`privacy_mode` | | For AI observability, capture usage metadata without prompt inputs or outputs. | False -`enable_full_ai_capture` | | Capture full AI content in PostHog AI wrapper events: skips string truncation and passes media (base64/data URIs) through unredacted. `privacy_mode` always wins. Wrapper events use the AI capture endpoint whether or not this is set. Defaults to False. | False -`before_send` | | Optional callback that can modify or drop events before upload. Return `None` to drop an event. Context tags, context options, `super_properties`, `super_options` and the values the SDK adds, such as `$is_server`, `$geoip_disable` and `$os`, fill in before it runs, so it can change or remove them. | None -`flag_fallback_cache_url` | | Optional feature flag fallback cache URL, such as `memory://local/?ttl=300&size=10000` or a Redis URL. | None -`enable_local_evaluation` | | Whether to poll feature flag definitions for local evaluation when a personal API key is configured. | True -`flag_definition_cache_provider` | Optional[FlagDefinitionCacheProvider] | Optional external cache provider for sharing feature flag definitions across workers. | None -`capture_exception_code_variables` | | Capture local variable values on exception stack frames. | False -`capture_trace_context` | | When OpenTelemetry is installed and a valid span is active at capture time, add its trace and span IDs as `$trace_id` and `$span_id` properties to events captured with `capture()` and `capture_ai()`, so they can be correlated with backend traces. They fill after context tags and `super_properties`, before `before_send`. A `$trace_id` or `$span_id` from the event, a context tag or `super_properties` stops the SDK adding either ID. Exception events (`capture_exception`) always attach these IDs regardless of this setting. Defaults to False. | False -`traces` | Optional[dict] | Config dict for distributed tracing: `service_name`, `service_version`, `environment`, `resource_attributes`, `flush_interval` (5 s), `max_queue_size` (2048), `max_export_batch_size` (512), `max_live_spans` (10000), `max_span_age` (3600 s), `max_attributes_per_span` (128), `max_events_per_span` (128), `max_attribute_value_length` (8192). `before_span_send` is a callable, or a list run in order, that receives each finished span as a dict (`trace_id`, `span_id` and `parent_span_id` are read-only) and returns it, edited, or `None` to drop it; a hook that raises drops the span. Tracing is off until this is provided. Spans export on a background timer even with `sync_mode`; serverless handlers should call `flush()` before returning. Defaults to None. | None -`code_variables_mask_patterns` | | Variable-name patterns to mask when capturing code variables. | None -`code_variables_ignore_patterns` | | Variable-name patterns to omit when capturing code variables. | None -`code_variables_mask_url_credentials` | | Scrub credentials embedded in URLs/DSNs (e.g. `user:pass@host`) from captured code variables, regardless of the surrounding variable name. Defaults to True. | None -`code_variables_detect_secrets` | | Last-resort entropy-based detection that redacts high-entropy secret-looking values (API keys, tokens, strong passwords) sitting in innocuously-named variables, after the name and URL checks. Skips structured ids (UUIDs, ObjectIds, hashes). Defaults to True. | None -`in_app_modules` | list[str] \| None | Module/package prefixes treated as in-app frames in captured exceptions. | None -`enable_exception_autocapture_rate_limiting` | | Rate limit autocaptured exceptions client-side with a token bucket per exception type. Disabled by default. | False -`exception_autocapture_bucket_size` | | Maximum burst of autocaptured exceptions allowed per exception type (token bucket size, clamped to 0-100). | DEFAULT_BUCKET_SIZE -`exception_autocapture_refill_rate` | | Tokens restored per refill interval for each exception type's bucket. | DEFAULT_REFILL_RATE -`exception_autocapture_refill_interval_seconds` | | Seconds between token refills for autocaptured exception rate limiting. | DEFAULT_REFILL_INTERVAL_SECONDS -`capture_compression` | Optional[Union[CaptureCompression, str]] | Request-body compression for `capture()` uploads. `CaptureCompression.GZIP` or `DEFLATE` (or the strings `"gzip"`/`"deflate"`). When omitted, the `POSTHOG_CAPTURE_COMPRESSION` env var is consulted, then no compression. | None -`capture_ai_compression` | Optional[Union[CaptureCompression, str]] | Request-body compression for `capture_ai()` uploads, set independently of `capture_compression`. Defaults to no compression, and the env var does not apply. `CaptureCompression.ZSTD` suits large AI payloads. | None -`capture_ai_max_queue_size` | int | Maximum number of AI events buffered before upload. Defaults to 1000, lower than `max_queue_size` because AI events are much larger. | 1000 -`capture_ai_timeout` | float | Seconds allowed for one AI upload request. Defaults to 30, longer than `timeout` because AI batches are much larger. | 30 -`capture_ai_max_event_bytes` | int | Largest serialized AI event the SDK sends; a larger one is dropped with an error log. Defaults to the AI endpoint's ceiling plus envelope headroom, and may only be lowered. | AI_MAX_MSG_SIZE +`project_api_key` | `str` | PostHog project API key/token. | *required* +`host` | | PostHog host. Defaults to the US ingestion endpoint when not set. App hosts such as `https://us.posthog.com` are mapped to the corresponding ingestion host. | `None` +`debug` | | Enable verbose SDK logging and re-raise errors from public API methods. | `False` +`max_queue_size` | | Maximum number of analytics events buffered before upload. AI events use `capture_ai_max_queue_size`. | `10000` +`send` | | If False, queueing succeeds but events are not sent. | `True` +`on_error` | | Optional callback `(error, batch)` invoked when an upload fails: by background consumers, or on the calling thread in `sync_mode`. Capture failures arrive as `CaptureError`. Without it, each failed batch logs one aggregate line. A capture that fails inside the callback logs that line instead of calling it again. Keep it short and non-blocking. Calling lifecycle methods directly is safe and deferred, but do not start another thread or task that calls `flush()`, `join()`, or `shutdown()` and then wait for it from the callback. | `None` +`flush_at` | | Number of queued events that triggers a batch upload. | `100` +`flush_interval` | | Maximum seconds a background consumer waits before flushing a partial batch. | `5.0` +`max_retries` | | Number of upload retries. Values below 0 are treated as 0. | `3` +`sync_mode` | | If True, send each event synchronously instead of using background worker threads. This blocks the calling thread; in asyncio applications such as FastAPI, use `AsyncPosthog` instead. | `False` +`timeout` | `float` | HTTP request timeout in seconds for event uploads. | `15` +`thread` | | Number of background consumer threads. | `1` +`poll_interval` | | Seconds between local feature flag definition refreshes. | `30` +`secret_key` | | A Personal API Key or Project Secret API Key, used to authenticate local feature flag evaluation, remote config payloads, and decrypted flag payloads. Example::
posthog.Client(project_api_key, secret_key="phx_...") | `None` +`personal_api_key` | | Deprecated alias for `secret_key`. Still honored for backwards compatibility; prefer `secret_key`, which also accepts a Project Secret API Key. | `None` +`disabled` | | If True, disable captures and API requests. Useful in tests. | `False` +`disable_geoip` | | Whether to disable server-side GeoIP enrichment. Defaults to True. Events get `$geoip_disable` only when the event, context tags and `super_properties` leave it unset. | `True` +`is_server` | | Whether events are emitted from a server-side runtime. Defaults to True; set to False when using the SDK as a client/CLI so the device OS is attributed to the person normally. Events get `$is_server` only when the event, context tags and `super_properties` leave it unset. | `True` +`historical_migration` | | Mark events as historical migration imports. | `False` +`feature_flags_request_timeout_seconds` | `float` | Timeout in seconds for feature flag and remote config requests. | `3` +`feature_flags_request_max_retries` | | Number of retries for feature flag requests after network, transport, or timeout failures. Defaults to 1. Set to 0 to disable retries. | `1` +`app_host` | `Optional[str]` | Your PostHog app host, such as `https://posthog.example.com`. `emails` sends there. Needed only when `host` is not PostHog Cloud, for example a reverse proxy, which must not receive the secret key. | `None` +`email_request_timeout_seconds` | `float` | Timeout in seconds for each email send request. Defaults to 15. | `15` +`email_request_max_retries` | `int` | Number of retries for an email send after a network error, a rate limit, or an unavailable service. Defaults to 2. Set to 0 when a job runner already retries the send. | `2` +`super_properties` | | Properties for every captured event. They fill only keys the event leaves unset, before `before_send` runs. `$set`, `$set_once`, `$groups` and `$group_set` fill one level deep. An event's own properties and context tags override them, and `before_send` can change or remove them. | `None` +`super_options` | | Capture options for every captured event, such as `{"cookieless_mode": True}`. They fill only options the event leaves unset, before `before_send` runs. An event's own `options` and context options override them, and `before_send` can change them. They also win over an event's legacy property for the same key, such as `$cookieless_mode`, so pass per-event overrides of that key as `options`. | `None` +`enable_exception_autocapture` | | Automatically capture uncaught exceptions. | `False` +`log_captured_exceptions` | | Also log exceptions captured by error tracking. | `False` +`project_root` | | Root path used to determine in-app stack frames for captured exceptions. Defaults to the current working directory. | `None` +`privacy_mode` | | For AI observability, capture usage metadata without prompt inputs or outputs. | `False` +`enable_full_ai_capture` | | Capture full AI content in PostHog AI wrapper events: skips string truncation and passes media (base64/data URIs) through unredacted. `privacy_mode` always wins. Wrapper events use the AI capture endpoint whether or not this is set. Defaults to False. | `False` +`before_send` | | Optional callback that can modify or drop events before upload. Return `None` to drop an event. Context tags, context options, `super_properties`, `super_options` and the values the SDK adds, such as `$is_server`, `$geoip_disable` and `$os`, fill in before it runs, so it can change or remove them. | `None` +`flag_fallback_cache_url` | | Optional feature flag fallback cache URL, such as `memory://local/?ttl=300&size=10000` or a Redis URL. | `None` +`enable_local_evaluation` | | Whether to poll feature flag definitions for local evaluation when a personal API key is configured. | `True` +`flag_definition_cache_provider` | `Optional[FlagDefinitionCacheProvider]` | Optional external cache provider for sharing feature flag definitions across workers. | `None` +`capture_exception_code_variables` | | Capture local variable values on exception stack frames. | `False` +`capture_trace_context` | | When OpenTelemetry is installed and a valid span is active at capture time, add its trace and span IDs as `$trace_id` and `$span_id` properties to events captured with `capture()` and `capture_ai()`, so they can be correlated with backend traces. They fill after context tags and `super_properties`, before `before_send`. A `$trace_id` or `$span_id` from the event, a context tag or `super_properties` stops the SDK adding either ID. Exception events (`capture_exception`) always attach these IDs regardless of this setting. Defaults to False. | `False` +`traces` | `Optional[dict]` | Config dict for distributed tracing: `service_name`, `service_version`, `environment`, `resource_attributes`, `flush_interval` (5 s), `max_queue_size` (2048), `max_export_batch_size` (512), `max_live_spans` (10000), `max_span_age` (3600 s), `max_attributes_per_span` (128), `max_events_per_span` (128), `max_attribute_value_length` (8192). `before_span_send` is a callable, or a list run in order, that receives each finished span as a dict (`trace_id`, `span_id` and `parent_span_id` are read-only) and returns it, edited, or `None` to drop it; a hook that raises drops the span. Tracing is off until this is provided. Spans export on a background timer even with `sync_mode`; serverless handlers should call `flush()` before returning. Defaults to None. | `None` +`code_variables_mask_patterns` | | Variable-name patterns to mask when capturing code variables. | `None` +`code_variables_ignore_patterns` | | Variable-name patterns to omit when capturing code variables. | `None` +`code_variables_mask_url_credentials` | | Scrub credentials embedded in URLs/DSNs (e.g. `user:pass@host`) from captured code variables, regardless of the surrounding variable name. Defaults to True. | `None` +`code_variables_detect_secrets` | | Last-resort entropy-based detection that redacts high-entropy secret-looking values (API keys, tokens, strong passwords) sitting in innocuously-named variables, after the name and URL checks. Skips structured ids (UUIDs, ObjectIds, hashes). Defaults to True. | `None` +`in_app_modules` | `list[str] \| None` | Module/package prefixes treated as in-app frames in captured exceptions. | `None` +`enable_exception_autocapture_rate_limiting` | | Rate limit autocaptured exceptions client-side with a token bucket per exception type. Disabled by default. | `False` +`exception_autocapture_bucket_size` | | Maximum burst of autocaptured exceptions allowed per exception type (token bucket size, clamped to 0-100). | `DEFAULT_BUCKET_SIZE` +`exception_autocapture_refill_rate` | | Tokens restored per refill interval for each exception type's bucket. | `DEFAULT_REFILL_RATE` +`exception_autocapture_refill_interval_seconds` | | Seconds between token refills for autocaptured exception rate limiting. | `DEFAULT_REFILL_INTERVAL_SECONDS` +`capture_compression` | `Optional[Union[CaptureCompression, str]]` | Request-body compression for `capture()` uploads. `CaptureCompression.GZIP` or `DEFLATE` (or the strings `"gzip"`/`"deflate"`). When omitted, the `POSTHOG_CAPTURE_COMPRESSION` env var is consulted, then no compression. | `None` +`capture_ai_compression` | `Optional[Union[CaptureCompression, str]]` | Request-body compression for `capture_ai()` uploads, set independently of `capture_compression`. Defaults to no compression, and the env var does not apply. `CaptureCompression.ZSTD` suits large AI payloads. | `None` +`capture_ai_max_queue_size` | `int` | Maximum number of AI events buffered before upload. Defaults to 1000, lower than `max_queue_size` because AI events are much larger. | `1000` +`capture_ai_timeout` | `float` | Seconds allowed for one AI upload request. Defaults to 30, longer than `timeout` because AI batches are much larger. | `30` +`capture_ai_max_event_bytes` | `int` | Largest serialized AI event the SDK sends; a larger one is dropped with an error log. Defaults to the AI endpoint's ceiling plus envelope headroom, and may only be lowered. | `AI_MAX_MSG_SIZE` **Examples:** @@ -986,8 +986,8 @@ Create a new context for managing shared state. Learn more about [contexts](/doc Name | Type | Description | Default ---- | ---- | ----------- | ------- -`fresh` | | Whether to create a fresh context that doesn't inherit from parent. | False -`capture_exceptions` | Optional[bool] | Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting. | None +`fresh` | | Whether to create a fresh context that doesn't inherit from parent. | `False` +`capture_exceptions` | `Optional[bool]` | Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting. | `None` **Examples:** @@ -1016,8 +1016,8 @@ Decorator that creates a new context for the wrapped function using this client. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`fresh` | | Whether to create a fresh context that doesn't inherit from parent. | False -`capture_exceptions` | Optional[bool] | Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting. | None +`fresh` | | Whether to create a fresh context that doesn't inherit from parent. | `False` +`capture_exceptions` | `Optional[bool]` | Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting. | `None`
Category @@ -1038,8 +1038,8 @@ Add a tag to the current context. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`name` | str | The tag key. | *required* -`value` | Any | The tag value. | *required* +`name` | `str` | The tag key. | *required* +`value` | `Any` | The tag value. | *required*
Category @@ -1060,7 +1060,7 @@ Get all tags from the current context. Type | Description ---- | ----------- -Dict[str, Any] | Dict of all tags in the current context. +`Dict[str, Any]` | Dict of all tags in the current context.
Category @@ -1086,8 +1086,8 @@ Context options fill options an event leaves unset, before Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The option name, such as `"process_person_profile"`. | *required* -`value` | Any | The option value, sent as given. | *required* +`key` | `str` | The option name, such as `"process_person_profile"`. | *required* +`value` | `Any` | The option value, sent as given. | *required*
Category @@ -1108,7 +1108,7 @@ Get all capture options from the current context. Type | Description ---- | ----------- -Dict[str, Any] | Dict of all capture options in the current context. +`Dict[str, Any]` | Dict of all capture options in the current context.
Category @@ -1129,7 +1129,7 @@ Identify the current context with a distinct ID. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`distinct_id` | str | The distinct ID to associate with the current context and its children. | *required* +`distinct_id` | `str` | The distinct ID to associate with the current context and its children. | *required*
Category @@ -1150,7 +1150,7 @@ Set the session ID for the current context. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`session_id` | str | The session ID to associate with the current context and its children. | *required* +`session_id` | `str` | The session ID to associate with the current context and its children. | *required*
Category @@ -1171,7 +1171,7 @@ Set the device ID for the current context. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`device_id` | str | The device ID to associate with the current context and its children. | *required* +`device_id` | `str` | The device ID to associate with the current context and its children. | *required*
Category @@ -1200,13 +1200,13 @@ Get feature flag payloads for a user, preserving valid serialized JSON. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None -`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None -`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None -`flag_keys_to_evaluate` | Optional[list[str]] | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | None -`device_id` | Optional[str] | The device ID for this request. | None +`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` +`flag_keys_to_evaluate` | `Optional[list[str]]` | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | `None` +`device_id` | `Optional[str]` | The device ID for this request. | `None` **Examples:** @@ -1233,13 +1233,13 @@ Get feature flags decision. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`distinct_id` | Optional[ID_TYPES] | The distinct ID of the user. | None -`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None -`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None -`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None -`flag_keys_to_evaluate` | Optional[list[str]] | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | None -`device_id` | Optional[str] | The device ID for this request. | None +`distinct_id` | `Optional[ID_TYPES]` | The distinct ID of the user. | `None` +`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` +`flag_keys_to_evaluate` | `Optional[list[str]]` | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | `None` +`device_id` | `Optional[str]` | The device ID for this request. | `None` **Examples:** @@ -1288,7 +1288,7 @@ Get the payload for a remote config feature flag. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The remote config feature flag key. | *required* +`key` | `str` | The remote config feature flag key. | *required* **Returns:** @@ -1325,16 +1325,16 @@ Return where a locally loaded feature flag is meant to be evaluated. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The feature flag key. | *required* +`key` | `str` | The feature flag key. | *required* **Returns:** Type | Description ---- | ----------- -Optional[FeatureFlagEvaluationRuntime] | The flag's evaluation runtime, or `None` when local evaluation has -Optional[FeatureFlagEvaluationRuntime] | not loaded a definition for this key. A definition that carries no -Optional[FeatureFlagEvaluationRuntime] | runtime reports `FeatureFlagEvaluationRuntime.ALL`, the default -Optional[FeatureFlagEvaluationRuntime] | PostHog applies. +`Optional[FeatureFlagEvaluationRuntime]` | The flag's evaluation runtime, or `None` when local evaluation has +`Optional[FeatureFlagEvaluationRuntime]` | not loaded a definition for this key. A definition that carries no +`Optional[FeatureFlagEvaluationRuntime]` | runtime reports `FeatureFlagEvaluationRuntime.ALL`, the default +`Optional[FeatureFlagEvaluationRuntime]` | PostHog applies. **Examples:** @@ -1370,14 +1370,14 @@ to a browser when a backend serves flags to its own frontend. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`evaluation_runtime` | Union[FeatureFlagEvaluationRuntime, str] | The runtime to match, as a `FeatureFlagEvaluationRuntime` or its string value. | *required* +`evaluation_runtime` | `Union[FeatureFlagEvaluationRuntime, str]` | The runtime to match, as a `FeatureFlagEvaluationRuntime` or its string value. | *required* **Returns:** Type | Description ---- | ----------- -list[str] | The matching flag keys, in the order local evaluation loaded them. -list[str] | Empty when no definitions are loaded. +`list[str]` | The matching flag keys, in the order local evaluation loaded them. +`list[str]` | Empty when no definitions are loaded. **Examples:** @@ -1463,7 +1463,7 @@ Name | Description Name | Type | Description ---- | ---- | ----------- `async_capable` | | -`capture_exceptions` | Optional[bool] | +`capture_exceptions` | `Optional[bool]` | `capture_tracing_headers` | | `client` | | `extra_properties` | | @@ -1473,7 +1473,7 @@ Name | Type | Description `request_filter` | | `sync_capable` | | `tag_map` | | Compatibility alias for `properties_map`. -`trust_tracing_headers` | bool | Deprecated alias for `capture_tracing_headers`. +`trust_tracing_headers` | `bool` | Deprecated alias for `capture_tracing_headers`. Initialize the middleware with Django's next handler. @@ -1648,13 +1648,13 @@ Add PostHog request context and exception tracking to a Flask app. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`app` | Optional[Flask] | An optional Flask application. If omitted, call `init_app` later (the Flask application-factory pattern). | None -`client` | Optional[Client] | Optional destination for automatically captured exceptions. The global client is used by default. Request context still enriches events captured through any client and does not redirect capture calls. | None -`capture_exceptions` | Optional[bool] | Capture exceptions that reach Flask's unhandled exception machinery. If omitted, inherit the effective client's `enable_exception_autocapture` setting. Pass `True` or `False` to override it. | None -`request_filter` | Optional[Callable[[Request], bool]] | Optional callback receiving Flask's request object. A false return value disables both context and exception capture for that request. | None -`extra_properties` | Optional[Callable[[Request], Mapping[str, Any]]] | Optional callback returning additional event properties. This is useful for application-specific metadata such as an authenticated user's role. Values should not contain secrets or request bodies. | None -`capture_tracing_headers` | Optional[bool] | Read PostHog distinct and session ID headers as request-scoped analytics context. Defaults to True. Explicit capture IDs and event properties take precedence. Never use these values for security. | None -`trust_tracing_headers` | Optional[bool] | Deprecated alias for `capture_tracing_headers`. The capture-named option takes precedence when both are supplied. | None +`app` | `Optional[Flask]` | An optional Flask application. If omitted, call `init_app` later (the Flask application-factory pattern). | `None` +`client` | `Optional[Client]` | Optional destination for automatically captured exceptions. The global client is used by default. Request context still enriches events captured through any client and does not redirect capture calls. | `None` +`capture_exceptions` | `Optional[bool]` | Capture exceptions that reach Flask's unhandled exception machinery. If omitted, inherit the effective client's `enable_exception_autocapture` setting. Pass `True` or `False` to override it. | `None` +`request_filter` | `Optional[Callable[[Request], bool]]` | Optional callback receiving Flask's request object. A false return value disables both context and exception capture for that request. | `None` +`extra_properties` | `Optional[Callable[[Request], Mapping[str, Any]]]` | Optional callback returning additional event properties. This is useful for application-specific metadata such as an authenticated user's role. Values should not contain secrets or request bodies. | `None` +`capture_tracing_headers` | `Optional[bool]` | Read PostHog distinct and session ID headers as request-scoped analytics context. Defaults to True. Explicit capture IDs and event properties take precedence. Never use these values for security. | `None` +`trust_tracing_headers` | `Optional[bool]` | Deprecated alias for `capture_tracing_headers`. The capture-named option takes precedence when both are supplied. | `None` The integration intentionally does not capture exceptions handled by an application error handler, expected HTTP exceptions, request or response @@ -1677,7 +1677,7 @@ Name | Type | Description `client` | | `extra_properties` | | `request_filter` | | -`trust_tracing_headers` | bool | Deprecated alias for `capture_tracing_headers`. +`trust_tracing_headers` | `bool` | Deprecated alias for `capture_tracing_headers`. #### `posthog.integrations.flask.PosthogFlaskIntegration.capture_exceptions` @@ -1737,13 +1737,13 @@ Add PostHog context and automatic exception capture to an ASGI app. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`app` | _ASGIApp | The downstream ASGI application. | *required* -`client` | Optional[Client] | Optional destination for automatically captured exceptions. The global client is used by default. Request context still enriches events captured through any client and does not redirect capture calls. | None -`capture_exceptions` | Optional[bool] | Capture exceptions escaping the downstream app. If omitted, inherit the effective client's `enable_exception_autocapture` setting. Pass `True` or `False` to override it. | None -`request_filter` | Optional[_RequestFilter] | Optional sync or async callback receiving the ASGI scope. Returning `False` bypasses all instrumentation for that scope. | None -`extra_properties` | Optional[_ExtraProperties] | Optional sync or async callback receiving the ASGI scope and returning additional event properties. | None -`capture_tracing_headers` | Optional[bool] | Read PostHog distinct and session ID headers as request-scoped analytics context. Defaults to True. Explicit capture IDs and event properties take precedence. Never use these values for security. | None -`trust_tracing_headers` | Optional[bool] | Deprecated alias for `capture_tracing_headers`. The capture-named option takes precedence when both are supplied. | None +`app` | `_ASGIApp` | The downstream ASGI application. | *required* +`client` | `Optional[Client]` | Optional destination for automatically captured exceptions. The global client is used by default. Request context still enriches events captured through any client and does not redirect capture calls. | `None` +`capture_exceptions` | `Optional[bool]` | Capture exceptions escaping the downstream app. If omitted, inherit the effective client's `enable_exception_autocapture` setting. Pass `True` or `False` to override it. | `None` +`request_filter` | `Optional[_RequestFilter]` | Optional sync or async callback receiving the ASGI scope. Returning `False` bypasses all instrumentation for that scope. | `None` +`extra_properties` | `Optional[_ExtraProperties]` | Optional sync or async callback receiving the ASGI scope and returning additional event properties. | `None` +`capture_tracing_headers` | `Optional[bool]` | Read PostHog distinct and session ID headers as request-scoped analytics context. Defaults to True. Explicit capture IDs and event properties take precedence. Never use these values for security. | `None` +`trust_tracing_headers` | `Optional[bool]` | Deprecated alias for `capture_tracing_headers`. The capture-named option takes precedence when both are supplied. | `None` **Attributes:** @@ -1755,7 +1755,7 @@ Name | Type | Description `client` | | `extra_properties` | | `request_filter` | | -`trust_tracing_headers` | bool | Deprecated alias for `capture_tracing_headers`. +`trust_tracing_headers` | `bool` | Deprecated alias for `capture_tracing_headers`. #### `posthog.integrations.asgi.PosthogASGIMiddleware.app` @@ -1813,11 +1813,11 @@ Celery integration that captures task lifecycle events and exceptions. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`client` | Optional[Client] | Optional `Client` instance. When provided, all events and exceptions are captured through this client rather than the global `posthog` module. Don't skip this if using a custom flag definition cache provider, and pass the custom `Client` instance here initialized with the custom provider so fork safety for that provider is handled correctly. | None -`capture_exceptions` | bool | Whether to capture task exceptions via `capture_exception` (default `True`). | True -`capture_task_lifecycle_events` | bool | Whether to emit lifecycle events of the task such as "started", "success", "failure" etc. (default `True`). | True -`propagate_context` | bool | Whether to propagate PostHog context (distinct ID, session ID, tags) from the producer to the worker via task headers (default `True`). | True -`task_filter` | Optional[Callable[[Optional[str], dict[str, Any]], bool]] | Optional callback `(task_name, task_properties) -> bool` expected to return `False` if a given task should not be tracked. | None +`client` | `Optional[Client]` | Optional `Client` instance. When provided, all events and exceptions are captured through this client rather than the global `posthog` module. Don't skip this if using a custom flag definition cache provider, and pass the custom `Client` instance here initialized with the custom provider so fork safety for that provider is handled correctly. | `None` +`capture_exceptions` | `bool` | Whether to capture task exceptions via `capture_exception` (default `True`). | `True` +`capture_task_lifecycle_events` | `bool` | Whether to emit lifecycle events of the task such as "started", "success", "failure" etc. (default `True`). | `True` +`propagate_context` | `bool` | Whether to propagate PostHog context (distinct ID, session ID, tags) from the producer to the worker via task headers (default `True`). | `True` +`task_filter` | `Optional[Callable[[Optional[str], dict[str, Any]], bool]]` | Optional callback `(task_name, task_properties) -> bool` expected to return `False` if a given task should not be tracked. | `None` **Functions:** @@ -1921,7 +1921,7 @@ to reset to the default socket behavior. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`socket_options` | Optional[SocketOptions] | A list of `(level, option, value)` tuples accepted by urllib3/`socket.setsockopt()`, or `None` to reset defaults. | *required* +`socket_options` | `Optional[SocketOptions]` | A list of `(level, option, value)` tuples accepted by urllib3/`socket.setsockopt()`, or `None` to reset defaults. | *required*
Example @@ -2120,9 +2120,9 @@ Create a new context scope that will be active for the duration of the with bloc Name | Type | Description | Default ---- | ---- | ----------- | ------- -`fresh` | bool | Whether to start with a fresh context (default: False) | False -`capture_exceptions` | Optional[bool] | Whether to capture exceptions raised within the context. If omitted, defaults to the relevant client's exception autocapture setting. | None -`client` | Optional[Client] | Optional Posthog client instance to use for this context (default: None) | None +`fresh` | `bool` | Whether to start with a fresh context (default: False) | `False` +`capture_exceptions` | `Optional[bool]` | Whether to capture exceptions raised within the context. If omitted, defaults to the relevant client's exception autocapture setting. | `None` +`client` | `Optional[Client]` | Optional Posthog client instance to use for this context (default: None) | `None` **Examples:** @@ -2152,8 +2152,8 @@ Decorator that creates a new context for the function. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`fresh` | | Whether to start with a fresh context (default: False) | False -`capture_exceptions` | Optional[bool] | Whether to capture and track exceptions with posthog error tracking. If omitted, defaults to the global exception autocapture setting. | None +`fresh` | | Whether to start with a fresh context (default: False) | `False` +`capture_exceptions` | `Optional[bool]` | Whether to capture and track exceptions with posthog error tracking. If omitted, defaults to the global exception autocapture setting. | `None` **Examples:** @@ -2184,7 +2184,7 @@ Set the session ID for the current context. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`session_id` | str | The session ID to associate with the current context and its children | *required* +`session_id` | `str` | The session ID to associate with the current context and its children | *required* **Examples:** @@ -2213,7 +2213,7 @@ in this or child contexts with the given device ID. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`device_id` | str | The device ID to associate with the current context and its children | *required* +`device_id` | `str` | The device ID to associate with the current context and its children | *required* **Examples:** @@ -2241,7 +2241,7 @@ Identify the current context with a distinct ID. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`distinct_id` | str | The distinct ID to associate with the current context and its children | *required* +`distinct_id` | `str` | The distinct ID to associate with the current context and its children | *required* **Examples:** @@ -2269,7 +2269,7 @@ Override code-variable capture for exceptions in the current context. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`enabled` | bool | Whether exceptions captured in this context should include local variable values from stack frames. | *required* +`enabled` | `bool` | Whether exceptions captured in this context should include local variable values from stack frames. | *required*
Category @@ -2290,7 +2290,7 @@ Override code-variable mask patterns for exceptions in the current context. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`mask_patterns` | list[str] | Variable-name patterns whose values should be replaced with `***` when code variables are captured. | *required* +`mask_patterns` | `list[str]` | Variable-name patterns whose values should be replaced with `***` when code variables are captured. | *required*
Category @@ -2311,7 +2311,7 @@ Override code-variable ignore patterns for exceptions in the current context. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`ignore_patterns` | list[str] | Variable-name patterns that should be omitted entirely when code variables are captured. | *required* +`ignore_patterns` | `list[str]` | Variable-name patterns that should be omitted entirely when code variables are captured. | *required*
Category @@ -2358,8 +2358,8 @@ Add a tag to the current context. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`name` | str | The tag key | *required* -`value` | Any | The tag value | *required* +`name` | `str` | The tag key | *required* +`value` | `Any` | The tag value | *required* **Examples:** @@ -2387,7 +2387,7 @@ Get all tags from the current context. Type | Description ---- | ----------- -Dict[str, Any] | Dict of all tags in the current context +`Dict[str, Any]` | Dict of all tags in the current context
Category @@ -2412,8 +2412,8 @@ own `options` override them, and `before_send` can change them. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The option name, such as `"process_person_profile"` | *required* -`value` | Any | The option value, sent as given | *required* +`key` | `str` | The option name, such as `"process_person_profile"` | *required* +`value` | `Any` | The option value, sent as given | *required* **Examples:** @@ -2442,7 +2442,7 @@ Get all capture options from the current context. Type | Description ---- | ----------- -Dict[str, Any] | Dict of all capture options in the current context +`Dict[str, Any]` | Dict of all capture options in the current context
Category @@ -2463,8 +2463,8 @@ Capture anything a user does within your system. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`event` | str | The event name to specify the event | *required* -`**kwargs` | Unpack[OptionalCaptureArgs] | Optional arguments including: distinct_id: Unique identifier for the user properties: Dict of event properties timestamp: When the event occurred. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. uuid: Unique identifier for this event. If omitted, one is generated and returned. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. groups: Dict of group types and IDs flags: A FeatureFlagEvaluations snapshot from evaluate_flags(). The exact values from the snapshot are attached with no extra /flags request. send_feature_flags: Deprecated. Prefer flags=... from evaluate_flags(). When truthy, evaluates flags during capture and attaches them to the event. disable_geoip: Whether to disable GeoIP lookup | {} +`event` | `str` | The event name to specify the event | *required* +`**kwargs` | `Unpack[OptionalCaptureArgs]` | Optional arguments including: distinct_id: Unique identifier for the user properties: Dict of event properties timestamp: When the event occurred. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. uuid: Unique identifier for this event. If omitted, one is generated and returned. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. groups: Dict of group types and IDs flags: A FeatureFlagEvaluations snapshot from evaluate_flags(). The exact values from the snapshot are attached with no extra /flags request. send_feature_flags: Deprecated. Prefer flags=... from evaluate_flags(). When truthy, evaluates flags during capture and attaches them to the event. disable_geoip: Whether to disable GeoIP lookup | `{}`
Details @@ -2537,8 +2537,8 @@ redaction or truncation is applied here. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`event` | str | The event name, normally one of the `$ai_*` event names. | *required* -`**kwargs` | Unpack[OptionalCaptureArgs] | Same optional arguments as `capture()`. | {} +`event` | `str` | The event name, normally one of the `$ai_*` event names. | *required* +`**kwargs` | `Unpack[OptionalCaptureArgs]` | Same optional arguments as `capture()`. | `{}` **Examples:** @@ -2564,7 +2564,7 @@ Set properties on a user record. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`**kwargs` | Unpack[OptionalSetArgs] | Optional arguments including: distinct_id: Unique identifier for the user. Falls back to the context distinct ID; if none exists, this call does nothing. properties: Dict of person properties to set. timestamp: When the properties were set. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. uuid: Unique identifier for this operation. If omitted, one is generated and returned. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. disable_geoip: Whether to disable GeoIP lookup. | {} +`**kwargs` | `Unpack[OptionalSetArgs]` | Optional arguments including: distinct_id: Unique identifier for the user. Falls back to the context distinct ID; if none exists, this call does nothing. properties: Dict of person properties to set. timestamp: When the properties were set. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. uuid: Unique identifier for this operation. If omitted, one is generated and returned. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. disable_geoip: Whether to disable GeoIP lookup. | `{}`
Details @@ -2593,7 +2593,7 @@ Set properties on a user record, only if they do not yet exist. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`**kwargs` | Unpack[OptionalSetArgs] | Optional arguments including: distinct_id: Unique identifier for the user. Falls back to the context distinct ID; if none exists, this call does nothing. properties: Dict of person properties to set only once. timestamp: When the properties were set. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. uuid: Unique identifier for this operation. If omitted, one is generated and returned. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. disable_geoip: Whether to disable GeoIP lookup. | {} +`**kwargs` | `Unpack[OptionalSetArgs]` | Optional arguments including: distinct_id: Unique identifier for the user. Falls back to the context distinct ID; if none exists, this call does nothing. properties: Dict of person properties to set only once. timestamp: When the properties were set. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. uuid: Unique identifier for this operation. If omitted, one is generated and returned. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. disable_geoip: Whether to disable GeoIP lookup. | `{}`
Details @@ -2623,14 +2623,14 @@ Set properties on a group. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`group_type` | str | Type of your group. Required - the call is dropped with a warning if it is missing or empty. | *required* -`group_key` | str | Unique identifier of the group. Required - the call is dropped with a warning if it is missing or empty. | *required* -`properties` | Optional[Dict[str, Any]] | Properties to set on the group | None -`timestamp` | Optional[Union[datetime, str]] | Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | None -`uuid` | Optional[str] | Optional UUID for the event | None -`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup | None -`distinct_id` | Optional[ID_TYPES] | Optional distinct ID of the user performing the action | None -`options` | Optional[Dict[str, Any]] | Optional capture options for the event, sent as given | None +`group_type` | `str` | Type of your group. Required - the call is dropped with a warning if it is missing or empty. | *required* +`group_key` | `str` | Unique identifier of the group. Required - the call is dropped with a warning if it is missing or empty. | *required* +`properties` | `Optional[Dict[str, Any]]` | Properties to set on the group | `None` +`timestamp` | `Optional[Union[datetime, str]]` | Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | `None` +`uuid` | `Optional[str]` | Optional UUID for the event | `None` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup | `None` +`distinct_id` | `Optional[ID_TYPES]` | Optional distinct ID of the user performing the action | `None` +`options` | `Optional[Dict[str, Any]]` | Optional capture options for the event, sent as given | `None` **Examples:** @@ -2655,12 +2655,12 @@ Associate user behaviour before and after they e.g. register, login, or perform Name | Type | Description | Default ---- | ---- | ----------- | ------- -`previous_id` | ID_TYPES | The unique ID of the user before | *required* -`distinct_id` | str | The current unique id | *required* -`timestamp` | Optional[Union[datetime, str]] | Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | None -`uuid` | Optional[str] | Optional UUID for the event | None -`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup | None -`options` | Optional[Dict[str, Any]] | Optional capture options for the event, sent as given | None +`previous_id` | `ID_TYPES` | The unique ID of the user before | *required* +`distinct_id` | `str` | The current unique id | *required* +`timestamp` | `Optional[Union[datetime, str]]` | Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | `None` +`uuid` | `Optional[str]` | Optional UUID for the event | `None` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup | `None` +`options` | `Optional[Dict[str, Any]]` | Optional capture options for the event, sent as given | `None`
Details @@ -2689,9 +2689,9 @@ Capture exceptions that happen in your code. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`exception` | Optional[ExceptionArg] | The exception to capture. If not provided, the current exception is captured via `sys.exc_info()` | None -`level` | Optional[str] | The `$exception_level`, such as `"warning"` or `"fatal"`. Defaults to `"error"`. An unknown value counts as unset. | None -`**kwargs` | Unpack[OptionalCaptureArgs] | Optional capture arguments including distinct_id, properties, timestamp, uuid, groups, flags, send_feature_flags, disable_geoip, and options. Reserved exception properties in `properties`, such as `$exception_list` and `$exception_level`, are ignored. | {} +`exception` | `Optional[ExceptionArg]` | The exception to capture. If not provided, the current exception is captured via `sys.exc_info()` | `None` +`level` | `Optional[str]` | The `$exception_level`, such as `"warning"` or `"fatal"`. Defaults to `"error"`. An unknown value counts as unset. | `None` +`**kwargs` | `Unpack[OptionalCaptureArgs]` | Optional capture arguments including distinct_id, properties, timestamp, uuid, groups, flags, send_feature_flags, disable_geoip, and options. Reserved exception properties in `properties`, such as `$exception_list` and `$exception_level`, are ignored. | `{}`
Details @@ -2723,15 +2723,15 @@ Use feature flags to enable or disable features for users. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The feature flag key | *required* -`distinct_id` | ID_TYPES | The user's distinct ID | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | Groups mapping | None -`person_properties` | Optional[Dict[str, Any]] | Person properties | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties | None -`only_evaluate_locally` | bool | Whether to evaluate only locally | False -`send_feature_flag_events` | bool | Whether to send feature flag events | True -`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup | None -`device_id` | Optional[str] | Optional device ID override for experience-continuity flags | None +`key` | `str` | The feature flag key | *required* +`distinct_id` | `ID_TYPES` | The user's distinct ID | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | Groups mapping | `None` +`person_properties` | `Optional[Dict[str, Any]]` | Person properties | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties | `None` +`only_evaluate_locally` | `bool` | Whether to evaluate only locally | `False` +`send_feature_flag_events` | `bool` | Whether to send feature flag events | `True` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup | `None` +`device_id` | `Optional[str]` | Optional device ID override for experience-continuity flags | `None`
Details @@ -2762,15 +2762,15 @@ Get feature flag variant for users. Used with experiments. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The feature flag key | *required* -`distinct_id` | ID_TYPES | The user's distinct ID | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | Groups mapping from group type to group key | None -`person_properties` | Optional[Dict[str, Any]] | Person properties | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties in format { group_type_name: { group_properties } } | None -`only_evaluate_locally` | bool | Whether to evaluate only locally | False -`send_feature_flag_events` | bool | Whether to send feature flag events | True -`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup | None -`device_id` | Optional[str] | Optional device ID override for experience-continuity flags | None +`key` | `str` | The feature flag key | *required* +`distinct_id` | `ID_TYPES` | The user's distinct ID | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | Groups mapping from group type to group key | `None` +`person_properties` | `Optional[Dict[str, Any]]` | Person properties | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties in format { group_type_name: { group_properties } } | `None` +`only_evaluate_locally` | `bool` | Whether to evaluate only locally | `False` +`send_feature_flag_events` | `bool` | Whether to send feature flag events | `True` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup | `None` +`device_id` | `Optional[str]` | Optional device ID override for experience-continuity flags | `None`
Details @@ -2801,14 +2801,14 @@ Get all flags for a given user. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`distinct_id` | ID_TYPES | The user's distinct ID | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | Groups mapping | None -`person_properties` | Optional[Dict[str, Any]] | Person properties | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties | None -`only_evaluate_locally` | bool | Whether to evaluate only locally | False -`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup | None -`device_id` | Optional[str] | Optional device ID override for experience-continuity flags | None -`flag_keys_to_evaluate` | Optional[list[str]] | Optional list of flag keys to evaluate (evaluates all if None) | None +`distinct_id` | `ID_TYPES` | The user's distinct ID | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | Groups mapping | `None` +`person_properties` | `Optional[Dict[str, Any]]` | Person properties | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties | `None` +`only_evaluate_locally` | `bool` | Whether to evaluate only locally | `False` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup | `None` +`device_id` | `Optional[str]` | Optional device ID override for experience-continuity flags | `None` +`flag_keys_to_evaluate` | `Optional[list[str]]` | Optional list of flag keys to evaluate (evaluates all if None) | `None`
Details @@ -2844,15 +2844,15 @@ This method evaluates a feature flag and returns a FeatureFlagResult object cont Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The feature flag key. | *required* -`distinct_id` | ID_TYPES | The user's distinct ID. | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | Mapping of group type to group key. | None -`person_properties` | Optional[Dict[str, Any]] | Person properties to use for evaluation. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties keyed by group type. | None -`only_evaluate_locally` | bool | Whether to evaluate only locally. | False -`send_feature_flag_events` | bool | Whether to send a $feature_flag_called event. | True -`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup. | None -`device_id` | Optional[str] | Optional device ID override for experience-continuity flags. | None +`key` | `str` | The feature flag key. | *required* +`distinct_id` | `ID_TYPES` | The user's distinct ID. | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | Mapping of group type to group key. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | Person properties to use for evaluation. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties keyed by group type. | `None` +`only_evaluate_locally` | `bool` | Whether to evaluate only locally. | `False` +`send_feature_flag_events` | `bool` | Whether to send a $feature_flag_called event. | `True` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup. | `None` +`device_id` | `Optional[str]` | Optional device ID override for experience-continuity flags. | `None` Example: ```python @@ -2878,24 +2878,24 @@ Deprecated for new code. Prefer `evaluate_flags()` and Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The feature flag key. | *required* -`distinct_id` | ID_TYPES | The user's distinct ID. | *required* -`match_value` | Optional[FlagValue] | Optional flag value to use when selecting a payload. | None -`groups` | Optional[Mapping[str, Union[str, int]]] | Mapping of group type to group key. | None -`person_properties` | Optional[Dict[str, Any]] | Person properties to use for evaluation. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties keyed by group type. | None -`only_evaluate_locally` | bool | Whether to evaluate only locally. | False -`send_feature_flag_events` | bool | Whether to send a $feature_flag_called event. | True -`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup. | None -`device_id` | Optional[str] | Optional device ID override for experience-continuity flags. | None +`key` | `str` | The feature flag key. | *required* +`distinct_id` | `ID_TYPES` | The user's distinct ID. | *required* +`match_value` | `Optional[FlagValue]` | Optional flag value to use when selecting a payload. | `None` +`groups` | `Optional[Mapping[str, Union[str, int]]]` | Mapping of group type to group key. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | Person properties to use for evaluation. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties keyed by group type. | `None` +`only_evaluate_locally` | `bool` | Whether to evaluate only locally. | `False` +`send_feature_flag_events` | `bool` | Whether to send a $feature_flag_called event. | `True` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup. | `None` +`device_id` | `Optional[str]` | Optional device ID override for experience-continuity flags. | `None` **Returns:** Type | Description ---- | ----------- -Optional[object] | The payload associated with the matched feature flag value, or None. -Optional[object] | This function returns the payload only, not the FeatureFlagResult wrapper -Optional[object] | used internally to compute it. +`Optional[object]` | The payload associated with the matched feature flag value, or None. +`Optional[object]` | This function returns the payload only, not the FeatureFlagResult wrapper +`Optional[object]` | used internally to compute it. ### `posthog.get_remote_config_payload` @@ -2909,7 +2909,7 @@ Get the payload for a remote config feature flag. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The key of the feature flag | *required* +`key` | `str` | The key of the feature flag | *required* **Returns:** @@ -2936,20 +2936,20 @@ Get all feature flag values and payloads for a user. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`distinct_id` | ID_TYPES | The user's distinct ID. | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | Mapping of group type to group key. | None -`person_properties` | Optional[Dict[str, Any]] | Person properties to use for evaluation. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties keyed by group type. | None -`only_evaluate_locally` | bool | Whether to evaluate only locally. | False -`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup. | None -`device_id` | Optional[str] | Optional device ID override for experience-continuity flags. | None -`flag_keys_to_evaluate` | Optional[list[str]] | Optional list of flag keys to evaluate. Evaluates all flags when omitted. | None +`distinct_id` | `ID_TYPES` | The user's distinct ID. | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | Mapping of group type to group key. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | Person properties to use for evaluation. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties keyed by group type. | `None` +`only_evaluate_locally` | `bool` | Whether to evaluate only locally. | `False` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup. | `None` +`device_id` | `Optional[str]` | Optional device ID override for experience-continuity flags. | `None` +`flag_keys_to_evaluate` | `Optional[list[str]]` | Optional list of flag keys to evaluate. Evaluates all flags when omitted. | `None` **Returns:** Type | Description ---- | ----------- -FlagsAndPayloads | A dict with `featureFlags` and `featureFlagPayloads` entries. +`FlagsAndPayloads` | A dict with `featureFlags` and `featureFlagPayloads` entries. ### `posthog.evaluate_flags` @@ -2970,14 +2970,14 @@ a single `/flags` request per incoming request. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`distinct_id` | Optional[ID_TYPES] | The user's distinct ID. If `None`, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot. | None -`groups` | Optional[Mapping[str, Union[str, int]]] | Mapping of group type to group key. | None -`person_properties` | Optional[Dict[str, Any]] | Person properties to use for evaluation. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties keyed by group type. | None -`only_evaluate_locally` | bool | If `True`, never fall back to remote evaluation and omit flags that cannot be evaluated locally. The snapshot's `unresolved_flags` lists them with a reason. | False -`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup. | None -`flag_keys` | Optional[list[str]] | Optional list that scopes local evaluation, the underlying `/flags` request, and the returned snapshot. When omitted or `None`, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per `evaluate_flags` call unless `only_evaluate_locally` is `True`. If the server also does not know the key, it is omitted from the snapshot. | None -`device_id` | Optional[str] | Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes. | None +`distinct_id` | `Optional[ID_TYPES]` | The user's distinct ID. If `None`, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot. | `None` +`groups` | `Optional[Mapping[str, Union[str, int]]]` | Mapping of group type to group key. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | Person properties to use for evaluation. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties keyed by group type. | `None` +`only_evaluate_locally` | `bool` | If `True`, never fall back to remote evaluation and omit flags that cannot be evaluated locally. The snapshot's `unresolved_flags` lists them with a reason. | `False` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup. | `None` +`flag_keys` | `Optional[list[str]]` | Optional list that scopes local evaluation, the underlying `/flags` request, and the returned snapshot. When omitted or `None`, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per `evaluate_flags` call unless `only_evaluate_locally` is `True`. If the server also does not know the key, it is omitted from the snapshot. | `None` +`device_id` | `Optional[str]` | Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes. | `None` **Examples:** @@ -3112,7 +3112,7 @@ rather than blocking the worker that invoked the callback. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`timeout_seconds` | Optional[float] | Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass `None` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently, waiting up to this many seconds for an in-flight flush before starting a fresh drain budget. A lock wait timeout leaves the in-flight flush to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. The last span request is not cut short and is bounded by the client's `timeout` in seconds, so this is not a strict wall-clock limit for the whole call. With `None`, a retriable span failure is left for a later flush rather than retried within this call. | 10 +`timeout_seconds` | `Optional[float]` | Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass `None` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently, waiting up to this many seconds for an in-flight flush before starting a fresh drain budget. A lock wait timeout leaves the in-flight flush to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. The last span request is not cut short and is bounded by the client's `timeout` in seconds, so this is not a strict wall-clock limit for the whole call. With `None`, a retriable span failure is left for a later flush rather than retried within this call. | `10` **Examples:** @@ -3160,12 +3160,12 @@ a usable handle, even when tracing is off, so calling code never branches. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`name` | str | A low-cardinality operation name, e.g. `GET /users/:id`. Variable values belong in attributes, not the name. | *required* -`kind` | Optional[str] | `internal` (default), `server`, `client`, `producer` or `consumer`. | None -`attributes` | Optional[Mapping[str, Any]] | Initial attributes. | None -`parent` | Union[Span, str, None] | A span handle, or an inbound W3C `traceparent` header value to continue a remote trace. Defaults to the active span. | None -`tracestate` | Optional[str] | The inbound `tracestate` header accompanying a `traceparent` string `parent`; preserved and propagated. | None -`start_time` | Union[datetime, float, None] | A `datetime` or epoch seconds, to backdate the span. | None +`name` | `str` | A low-cardinality operation name, e.g. `GET /users/:id`. Variable values belong in attributes, not the name. | *required* +`kind` | `Optional[str]` | `internal` (default), `server`, `client`, `producer` or `consumer`. | `None` +`attributes` | `Optional[Mapping[str, Any]]` | Initial attributes. | `None` +`parent` | `Union[Span, str, None]` | A span handle, or an inbound W3C `traceparent` header value to continue a remote trace. Defaults to the active span. | `None` +`tracestate` | `Optional[str]` | The inbound `tracestate` header accompanying a `traceparent` string `parent`; preserved and propagated. | `None` +`start_time` | `Union[datetime, float, None]` | A `datetime` or epoch seconds, to backdate the span. | `None` **Examples:** @@ -3251,15 +3251,15 @@ Most applications should either instantiate `Posthog` directly or set Type | Description ---- | ----------- -Client | The global `Client` instance. If both `api_key` and -Client | `project_api_key` are missing or blank, the client is disabled and -Client | module-level calls become no-ops. +`Client` | The global `Client` instance. If both `api_key` and +`Client` | `project_api_key` are missing or blank, the client is disabled and +`Client` | module-level calls become no-ops. ## Types ### `OptionalCaptureArgs` -Bases: TypedDict +Bases: `TypedDict` Optional arguments for the capture method. @@ -3281,15 +3281,15 @@ Name | Type | Description | Default Name | Type | Description ---- | ---- | ----------- -`disable_geoip` | NotRequired[Optional[bool]] | -`distinct_id` | NotRequired[Optional[ID_TYPES]] | -`flags` | NotRequired[Optional[FeatureFlagEvaluations]] | -`groups` | NotRequired[Optional[Dict[str, str]]] | -`options` | NotRequired[Optional[Dict[str, Any]]] | -`properties` | NotRequired[Optional[Dict[str, Any]]] | -`send_feature_flags` | NotRequired[Optional[Union[bool, SendFeatureFlagsOptions]]] | -`timestamp` | NotRequired[Optional[Union[datetime, str]]] | -`uuid` | NotRequired[Optional[Union[str, UUID]]] | +`disable_geoip` | `NotRequired[Optional[bool]]` | +`distinct_id` | `NotRequired[Optional[ID_TYPES]]` | +`flags` | `NotRequired[Optional[FeatureFlagEvaluations]]` | +`groups` | `NotRequired[Optional[Dict[str, str]]]` | +`options` | `NotRequired[Optional[Dict[str, Any]]]` | +`properties` | `NotRequired[Optional[Dict[str, Any]]]` | +`send_feature_flags` | `NotRequired[Optional[Union[bool, SendFeatureFlagsOptions]]]` | +`timestamp` | `NotRequired[Optional[Union[datetime, str]]]` | +`uuid` | `NotRequired[Optional[Union[str, UUID]]]` | #### `posthog.args.OptionalCaptureArgs.disable_geoip` @@ -3383,8 +3383,8 @@ Name | Description Name | Type | Description ---- | ---- | ----------- -`keys` | List[str] | Return the flag keys that are part of this evaluation. -`unresolved_flags` | Mapping[str, UnresolvedFlagReason] | Return a read-only mapping of flag key to the reason local evaluation +`keys` | `List[str]` | Return the flag keys that are part of this evaluation. +`unresolved_flags` | `Mapping[str, UnresolvedFlagReason]` | Return a read-only mapping of flag key to the reason local evaluation Internal — instances are created by the SDK via `Client.evaluate_flags()`. @@ -3423,8 +3423,8 @@ first access per (distinct_id, flag, value) tuple, deduped via the SDK's cache. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The feature flag key. | *required* -`default_value` | bool | Returned when the flag has no value in this evaluation — the key was not part of the evaluated set, or the evaluation itself came back empty (failed `/flags` request, quota limit, or no resolvable `distinct_id`). Defaults to `False`. | False +`key` | `str` | The feature flag key. | *required* +`default_value` | `bool` | Returned when the flag has no value in this evaluation — the key was not part of the evaluated set, or the evaluation itself came back empty (failed `/flags` request, quota limit, or no resolvable `distinct_id`). Defaults to `False`. | `False` A flag that has a value always wins over `default_value`, including a disabled flag, which returns `False` even when `default_value=True`. @@ -3488,11 +3488,11 @@ The result of calling a feature flag which includes the flag result, variant, an Name | Type | Description ---- | ---- | ----------- -`key` | str | The unique identifier of the feature flag. -`enabled` | bool | Whether the feature flag is enabled for the current context. -`variant` | Optional[str] | The variant value if the flag is enabled and has variants, None otherwise. -`payload` | Optional[Any] | Additional data associated with the feature flag, if any. -`reason` | Optional[str] | A description of why the flag was enabled or disabled, if available. +`key` | `str` | The unique identifier of the feature flag. +`enabled` | `bool` | Whether the feature flag is enabled for the current context. +`variant` | `Optional[str]` | The variant value if the flag is enabled and has variants, None otherwise. +`payload` | `Optional[Any]` | Additional data associated with the feature flag, if any. +`reason` | `Optional[str]` | A description of why the flag was enabled or disabled, if available. **Functions:** @@ -3520,14 +3520,14 @@ Create a FeatureFlagResult from a FeatureFlag object. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`details` | Union[FeatureFlag, None] | The FeatureFlag object to convert. | *required* -`override_match_value` | Optional[FlagValue] | If provided, this value will be used to populate the enabled and variant fields instead of the values from the FeatureFlag. | None +`details` | `Union[FeatureFlag, None]` | The FeatureFlag object to convert. | *required* +`override_match_value` | `Optional[FlagValue]` | If provided, this value will be used to populate the enabled and variant fields instead of the values from the FeatureFlag. | `None` **Returns:** Type | Description ---- | ----------- -FeatureFlagResult \| None | FeatureFlagResult | None: A new FeatureFlagResult instance, or None if details is None. +`FeatureFlagResult \| None` | FeatureFlagResult | None: A new FeatureFlagResult instance, or None if details is None. #### `posthog.types.FeatureFlagResult.from_value_and_payload` @@ -3541,15 +3541,15 @@ Creates a FeatureFlagResult from a flag value and payload. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The unique identifier of the feature flag. | *required* -`value` | Union[FlagValue, None] | The value of the flag (string variant or boolean). | *required* -`payload` | Any | Additional data associated with the feature flag. | *required* +`key` | `str` | The unique identifier of the feature flag. | *required* +`value` | `Union[FlagValue, None]` | The value of the flag (string variant or boolean). | *required* +`payload` | `Any` | Additional data associated with the feature flag. | *required* **Returns:** Type | Description ---- | ----------- -Union[FeatureFlagResult, None] | Union[FeatureFlagResult, None]: A new FeatureFlagResult instance, or None if value is None. +`Union[FeatureFlagResult, None]` | Union[FeatureFlagResult, None]: A new FeatureFlagResult instance, or None if value is None. #### `posthog.types.FeatureFlagResult.get_value` @@ -3564,7 +3564,7 @@ This is the value we report as `$feature_flag_response` in the `$feature_flag_ca Name | Type | Description ---- | ---- | ----------- -`FlagValue` | FlagValue | Either a string variant or boolean value representing the flag's state. +`FlagValue` | `FlagValue` | Either a string variant or boolean value representing the flag's state. #### `posthog.types.FeatureFlagResult.key` @@ -3602,11 +3602,11 @@ Detailed feature flag evaluation returned by the flags API. Name | Type | Description ---- | ---- | ----------- -`key` | str | Feature flag key. -`enabled` | bool | Whether the flag is enabled for the evaluated user or group. -`variant` | Optional[str] | Variant key for multivariate flags, otherwise `None`. -`reason` | Optional[FlagReason] | Optional reason metadata explaining the result. -`metadata` | Union[FlagMetadata, LegacyFlagMetadata] | Payload and other metadata returned by the API. +`key` | `str` | Feature flag key. +`enabled` | `bool` | Whether the flag is enabled for the evaluated user or group. +`variant` | `Optional[str]` | Variant key for multivariate flags, otherwise `None`. +`reason` | `Optional[FlagReason]` | Optional reason metadata explaining the result. +`metadata` | `Union[FlagMetadata, LegacyFlagMetadata]` | Payload and other metadata returned by the API. **Functions:** @@ -3672,7 +3672,7 @@ variant: Optional[str] ### `FlagsAndPayloads` -Bases: TypedDict +Bases: `TypedDict` Feature flag values and payloads keyed by feature flag key. @@ -3680,8 +3680,8 @@ Feature flag values and payloads keyed by feature flag key. Name | Type | Description ---- | ---- | ----------- -`featureFlags` | Optional[dict[str, FlagValue]] | Each flag's value, keyed by flag key. -`featureFlagPayloads` | Optional[dict[str, Any]] | Each flag's payload, keyed by flag key. +`featureFlags` | `Optional[dict[str, FlagValue]]` | Each flag's value, keyed by flag key. +`featureFlagPayloads` | `Optional[dict[str, Any]]` | Each flag's payload, keyed by flag key. #### `posthog.types.FlagsAndPayloads.featureFlagPayloads` @@ -3697,7 +3697,7 @@ featureFlags: Optional[dict[str, FlagValue]] ### `UnresolvedFlagReason` -Bases: str, Enum +Bases: `str`, `Enum` Why local evaluation left a flag without a value. @@ -3760,7 +3760,7 @@ UNSUPPORTED_DEFINITION = 'unsupported_definition' InconclusiveMatchError(*args, reason: Optional[UnresolvedFlagReason] = None) ``` -Bases: Exception +Bases: `Exception` Raised when local evaluation can't determine a flag's value. `reason` says why, as reported by `FeatureFlagEvaluations.unresolved_flags`. @@ -3769,7 +3769,7 @@ why, as reported by `FeatureFlagEvaluations.unresolved_flags`. Name | Type | Description ---- | ---- | ----------- -`reason` | UnresolvedFlagReason | Why local evaluation couldn't resolve the flag. +`reason` | `UnresolvedFlagReason` | Why local evaluation couldn't resolve the flag. #### `posthog.feature_flags.InconclusiveMatchError.reason` @@ -3783,7 +3783,7 @@ reason: UnresolvedFlagReason = UnresolvedFlagReason.UNSUPPORTED_DEFINITION RequiresServerEvaluation(*args, reason: Optional[UnresolvedFlagReason] = None) ``` -Bases: Exception +Bases: `Exception` Raised when feature flag evaluation requires server-side data that is not available locally (e.g., static cohorts). @@ -3795,7 +3795,7 @@ InconclusiveMatchError which allows trying other conditions. Name | Type | Description ---- | ---- | ----------- -`reason` | UnresolvedFlagReason | Why the flag needs server-side evaluation. +`reason` | `UnresolvedFlagReason` | Why the flag needs server-side evaluation. #### `posthog.feature_flags.RequiresServerEvaluation.reason` @@ -3809,7 +3809,7 @@ reason: UnresolvedFlagReason = UnresolvedFlagReason.UNSUPPORTED_DEFINITION CaptureError(status: int | str, message: str, *, endpoint: str, retry_after: Optional[float] = None, request_id: Optional[str] = None, attempts: Optional[int] = None, retry_exhausted: Optional[list[str]] = None, drops: Optional[list[tuple[str, Optional[str]]]] = None, event_results: Optional[dict[str, CaptureEventResult]] = None) ``` -Bases: APIError +Bases: `APIError` A capture batch that was not fully delivered. @@ -4095,7 +4095,7 @@ span.update_name(f"{request.method} {route.pattern}") Emails(secret_key: Optional[str], *, host: Optional[str] = None, timeout: float = 15, max_retries: int = 2, disabled: bool = False, session: Optional[requests.Session] = None) -> None ``` -Bases: _EmailsBase +Bases: `_EmailsBase` Sends transactional email from PostHog email templates. @@ -4153,20 +4153,20 @@ Read what happened to an email that `send` queued. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`message_id` | Union[str, UUID] | The `id` of the `SendEmailResult` that `send` returned. | *required* +`message_id` | `Union[str, UUID]` | The `id` of the `SendEmailResult` that `send` returned. | *required* **Returns:** Type | Description ---- | ----------- -EmailStatus | The email's latest status, for 30 days after the send. +`EmailStatus` | The email's latest status, for 30 days after the send. **Raises:** Type | Description ---- | ----------- -EmailError | PostHog did not answer with a status. `code` is `not_found` for an unknown ID, an email older than 30 days, or for a few seconds after the send. -ValueError | `message_id` is not a message ID. +`EmailError` | PostHog did not answer with a status. `code` is `not_found` for an unknown ID, an email older than 30 days, or for a few seconds after the send. +`ValueError` | `message_id` is not a message ID. **Examples:** @@ -4207,26 +4207,26 @@ Queue one email from a transactional email template. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`to` | str | One recipient email address. | *required* -`template_id` | Union[str, UUID] | The UUID of a template made transactional in PostHog. | *required* -`template_data` | Optional[Mapping[str, Any]] | Values for the template's `{{ variables.* }}` placeholders. Values are encoded the way capture encodes properties. | None -`distinct_id` | Optional[str] | The recipient's distinct ID in your product. The email's events go to this person. Without it, they go to a person whose distinct ID is the email address. | None -`idempotency_key` | Optional[str] | 1 to 255 visible ASCII characters. A send with a key that PostHog has already accepted queues nothing and returns the first send's result. Without it, the client makes a key that covers its own retries. Pass a key derived from what the email is for, such as `welcome:user_123`, to cover retries outside this call, too. | None -`max_retries` | Optional[int] | Overrides the client's maximum number of retries for this send. `0` turns retries off. | None +`to` | `str` | One recipient email address. | *required* +`template_id` | `Union[str, UUID]` | The UUID of a template made transactional in PostHog. | *required* +`template_data` | `Optional[Mapping[str, Any]]` | Values for the template's `{{ variables.* }}` placeholders. Values are encoded the way capture encodes properties. | `None` +`distinct_id` | `Optional[str]` | The recipient's distinct ID in your product. The email's events go to this person. Without it, they go to a person whose distinct ID is the email address. | `None` +`idempotency_key` | `Optional[str]` | 1 to 255 visible ASCII characters. A send with a key that PostHog has already accepted queues nothing and returns the first send's result. Without it, the client makes a key that covers its own retries. Pass a key derived from what the email is for, such as `welcome:user_123`, to cover retries outside this call, too. | `None` +`max_retries` | `Optional[int]` | Overrides the client's maximum number of retries for this send. `0` turns retries off. | `None` **Returns:** Type | Description ---- | ----------- -SendEmailResult | The queued email. A result means PostHog queued the email, not that -SendEmailResult | it was delivered. +`SendEmailResult` | The queued email. A result means PostHog queued the email, not that +`SendEmailResult` | it was delivered. **Raises:** Type | Description ---- | ----------- -EmailError | PostHog did not queue the email, or the client could not tell whether it did. -(TypeError, ValueError) | An argument has the wrong type or format. +`EmailError` | PostHog did not queue the email, or the client could not tell whether it did. +`(TypeError, ValueError)` | An argument has the wrong type or format. **Examples:** @@ -4252,7 +4252,7 @@ timeout = timeout EmailError(*, name: str, code: str, message: str, status_code: Optional[int] = None, why: Optional[str] = None, fix: Optional[str] = None, docs_url: Optional[str] = None, field: Optional[str] = None, retry_after: Optional[float] = None) -> None ``` -Bases: Exception +Bases: `Exception` PostHog did not queue the email, or the client could not tell whether it did. @@ -4340,9 +4340,9 @@ The queued email. `id` is its message ID. Name | Type | Description ---- | ---- | ----------- -`id` | str | The email's message ID. -`replayed` | bool | `True` when the response says PostHog replayed an earlier send for the same `idempotency_key` instead of sending again. -`warnings` | tuple[EmailWarning, ...] | Warnings PostHog returned about the request. +`id` | `str` | The email's message ID. +`replayed` | `bool` | `True` when the response says PostHog replayed an earlier send for the same `idempotency_key` instead of sending again. +`warnings` | `tuple[EmailWarning, ...]` | Warnings PostHog returned about the request. #### `posthog.emails.SendEmailResult.id` diff --git a/references/posthog-python-reference.md b/references/posthog-python-reference.md index 940600cf7..77b863554 100644 --- a/references/posthog-python-reference.md +++ b/references/posthog-python-reference.md @@ -139,12 +139,12 @@ code never branches. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`name` | str | A low-cardinality operation name, e.g. `GET /users/:id`. Variable values belong in attributes, not the name. | *required* -`kind` | Optional[str] | `internal` (default), `server`, `client`, `producer` or `consumer`. | None -`attributes` | Optional[Mapping[str, Any]] | Initial attributes. | None -`parent` | Union[Span, str, None] | A span handle, or an inbound W3C `traceparent` header value to continue a remote trace. Defaults to the active span. A forked child starts with no active span; pass the parent span to continue a trace across a fork. | None -`tracestate` | Optional[str] | The inbound `tracestate` header accompanying a `traceparent` string `parent`; preserved and propagated. | None -`start_time` | Union[datetime, float, None] | A `datetime` or epoch seconds, to backdate the span. | None +`name` | `str` | A low-cardinality operation name, e.g. `GET /users/:id`. Variable values belong in attributes, not the name. | *required* +`kind` | `Optional[str]` | `internal` (default), `server`, `client`, `producer` or `consumer`. | `None` +`attributes` | `Optional[Mapping[str, Any]]` | Initial attributes. | `None` +`parent` | `Union[Span, str, None]` | A span handle, or an inbound W3C `traceparent` header value to continue a remote trace. Defaults to the active span. A forked child starts with no active span; pass the parent span to continue a trace across a fork. | `None` +`tracestate` | `Optional[str]` | The inbound `tracestate` header accompanying a `traceparent` string `parent`; preserved and propagated. | `None` +`start_time` | `Union[datetime, float, None]` | A `datetime` or epoch seconds, to backdate the span. | `None` **Examples:** @@ -192,11 +192,11 @@ Create an alias between two distinct IDs. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`previous_id` | ID_TYPES | The previous distinct ID. Required - the call is dropped with a warning if it is missing or empty. | *required* -`distinct_id` | Optional[str] | The new distinct ID to alias to. Falls back to the context distinct ID; the call is dropped with a warning if neither is available. | *required* -`timestamp` | Optional[Union[datetime, str]] | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | None -`uuid` | Optional[str] | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | None -`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this event. | None +`previous_id` | `ID_TYPES` | The previous distinct ID. Required - the call is dropped with a warning if it is missing or empty. | *required* +`distinct_id` | `Optional[str]` | The new distinct ID to alias to. Falls back to the context distinct ID; the call is dropped with a warning if neither is available. | *required* +`timestamp` | `Optional[Union[datetime, str]]` | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | `None` +`uuid` | `Optional[str]` | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | `None` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this event. | `None` **Examples:** @@ -220,7 +220,7 @@ Captures an event manually. [Learn about capture best practices](https://posthog Name | Type | Description | Default ---- | ---- | ----------- | ------- -`event` | str | The event name to capture. | *required* +`event` | `str` | The event name to capture. | *required* `distinct_id` | | The distinct ID of the user. | `properties` | | A dictionary of properties to include with the event. | `timestamp` | | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | @@ -298,7 +298,7 @@ span IDs are added as `$trace_id` and `$span_id` event properties. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`exception` | Optional[ExceptionArg] | The exception to capture. | *required* +`exception` | `Optional[ExceptionArg]` | The exception to capture. | *required* `distinct_id` | | The distinct ID of the user. | `properties` | | A dictionary of additional properties. Overriding reserved exception properties is deprecated and will stop working in the next major version. | `flags` | | A `FeatureFlagEvaluations` snapshot from `evaluate_flags()`. Attaches those exact flag values to the captured `$exception` event. | @@ -368,7 +368,7 @@ deferred rather than blocking the worker that invoked the callback. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`timeout_seconds` | Optional[float] | Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass `None` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently. The span exporter waits up to this many seconds for an in-flight flush, then uses a fresh drain budget once it acquires the lock. If the lock wait times out, the in-flight flush is left to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. Each span request is bounded by the client's `timeout` in seconds, and the last request is not cut short. This is therefore not a strict wall-clock limit for the whole call. With `None`, a retriable span failure is left for a later flush rather than retried within this call. | 10 +`timeout_seconds` | `Optional[float]` | Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass `None` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently. The span exporter waits up to this many seconds for an in-flight flush, then uses a fresh drain budget once it acquires the lock. If the lock wait times out, the in-flight flush is left to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. Each span request is bounded by the client's `timeout` in seconds, and the last request is not cut short. This is therefore not a strict wall-clock limit for the whole call. With `None`, a retriable span failure is left for a later flush rather than retried within this call. | `10` **Examples:** @@ -392,15 +392,15 @@ Get multivariate feature flag value for a user. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The feature flag key. | *required* -`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None -`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None -`only_evaluate_locally` | bool | Whether to only evaluate locally. | False -`send_feature_flag_events` | bool | Whether to send feature flag events. | True -`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None -`device_id` | Optional[str] | The device ID for this request. | None +`key` | `str` | The feature flag key. | *required* +`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` +`only_evaluate_locally` | `bool` | Whether to only evaluate locally. | `False` +`send_feature_flag_events` | `bool` | Whether to send feature flag events. | `True` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` +`device_id` | `Optional[str]` | The device ID for this request. | `None` **Examples:** @@ -426,24 +426,24 @@ Get the payload for a feature flag. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The feature flag key. | *required* -`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* -`match_value` | Optional[FlagValue] | The specific flag value to get payload for. | None -`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None -`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None -`only_evaluate_locally` | bool | Whether to only evaluate locally. | False -`send_feature_flag_events` | bool | Deprecated. Use get_feature_flag() instead if you need events. | False -`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None -`device_id` | Optional[str] | The device ID for this request. | None +`key` | `str` | The feature flag key. | *required* +`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* +`match_value` | `Optional[FlagValue]` | The specific flag value to get payload for. | `None` +`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` +`only_evaluate_locally` | `bool` | Whether to only evaluate locally. | `False` +`send_feature_flag_events` | `bool` | Deprecated. Use get_feature_flag() instead if you need events. | `False` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` +`device_id` | `Optional[str]` | The device ID for this request. | `None` **Returns:** Type | Description ---- | ----------- -Optional[object] | The payload associated with the matched feature flag value, or None. -Optional[object] | This method returns the payload only, not the FeatureFlagResult wrapper -Optional[object] | used internally to compute it. +`Optional[object]` | The payload associated with the matched feature flag value, or None. +`Optional[object]` | This method returns the payload only, not the FeatureFlagResult wrapper +`Optional[object]` | used internally to compute it. **Examples:** @@ -482,21 +482,21 @@ if flag_result and flag_result.get_value() == 'variant-key': Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The feature flag key. | *required* -`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None -`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None -`only_evaluate_locally` | bool | Whether to only evaluate locally. | False -`send_feature_flag_events` | bool | Whether to send feature flag events. | True -`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None -`device_id` | Optional[str] | The device ID for this request. | None +`key` | `str` | The feature flag key. | *required* +`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` +`only_evaluate_locally` | `bool` | Whether to only evaluate locally. | `False` +`send_feature_flag_events` | `bool` | Whether to send feature flag events. | `True` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` +`device_id` | `Optional[str]` | The device ID for this request. | `None` **Returns:** Type | Description ---- | ----------- -Optional[FeatureFlagResult] | Optional[FeatureFlagResult]: The feature flag result or None if disabled/not found. +`Optional[FeatureFlagResult]` | Optional[FeatureFlagResult]: The feature flag result or None if disabled/not found. ## Get feature flags @@ -512,13 +512,13 @@ Get feature flag variants for a user. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None -`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None -`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None -`flag_keys_to_evaluate` | Optional[list[str]] | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | None -`device_id` | Optional[str] | The device ID for this request. | None +`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` +`flag_keys_to_evaluate` | `Optional[list[str]]` | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | `None` +`device_id` | `Optional[str]` | The device ID for this request. | `None` ### `Client.get_all_flags` @@ -532,14 +532,14 @@ Get all feature flags for a user. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None -`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None -`only_evaluate_locally` | bool | Whether to only evaluate locally. | False -`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None -`flag_keys_to_evaluate` | Optional[list[str]] | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | None -`device_id` | Optional[str] | The device ID for this request. | None +`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` +`only_evaluate_locally` | `bool` | Whether to only evaluate locally. | `False` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` +`flag_keys_to_evaluate` | `Optional[list[str]]` | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | `None` +`device_id` | `Optional[str]` | The device ID for this request. | `None` **Examples:** @@ -561,13 +561,13 @@ Get feature flags and payloads for a user. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None -`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None -`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None -`flag_keys_to_evaluate` | Optional[list[str]] | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | None -`device_id` | Optional[str] | The device ID for this request. | None +`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` +`flag_keys_to_evaluate` | `Optional[list[str]]` | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | `None` +`device_id` | `Optional[str]` | The device ID for this request. | `None` **Examples:** @@ -587,14 +587,14 @@ Get all feature flags and their payloads for a user. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None -`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None -`only_evaluate_locally` | bool | Whether to only evaluate locally. | False -`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None -`flag_keys_to_evaluate` | Optional[list[str]] | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | None -`device_id` | Optional[str] | The device ID for this request. | None +`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` +`only_evaluate_locally` | `bool` | Whether to only evaluate locally. | `False` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` +`flag_keys_to_evaluate` | `Optional[list[str]]` | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | `None` +`device_id` | `Optional[str]` | The device ID for this request. | `None` **Examples:** @@ -627,20 +627,20 @@ and reason `"Evaluated locally"`. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`distinct_id` | Optional[ID_TYPES] | The user's distinct ID. If `None`, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot. | None -`groups` | Optional[Mapping[str, Union[str, int]]] | Mapping of group type to group key. | None -`person_properties` | Optional[Dict[str, Any]] | Person properties to use for evaluation. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties keyed by group type. | None -`only_evaluate_locally` | bool | If True, never fall back to remote evaluation — flags that can't be evaluated locally are omitted from the snapshot and listed with a reason in its `unresolved_flags`. | False -`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup. | None -`flag_keys` | Optional[List[str]] | Optional list that scopes local evaluation, the underlying `/flags` request, and the returned snapshot. When omitted or `None`, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per `evaluate_flags` call unless `only_evaluate_locally` is True. If the server also does not know the key, it is omitted from the snapshot. | None -`device_id` | Optional[str] | Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes. | None +`distinct_id` | `Optional[ID_TYPES]` | The user's distinct ID. If `None`, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot. | `None` +`groups` | `Optional[Mapping[str, Union[str, int]]]` | Mapping of group type to group key. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | Person properties to use for evaluation. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties keyed by group type. | `None` +`only_evaluate_locally` | `bool` | If True, never fall back to remote evaluation — flags that can't be evaluated locally are omitted from the snapshot and listed with a reason in its `unresolved_flags`. | `False` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup. | `None` +`flag_keys` | `Optional[List[str]]` | Optional list that scopes local evaluation, the underlying `/flags` request, and the returned snapshot. When omitted or `None`, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per `evaluate_flags` call unless `only_evaluate_locally` is True. If the server also does not know the key, it is omitted from the snapshot. | `None` +`device_id` | `Optional[str]` | Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes. | `None` **Returns:** Type | Description ---- | ----------- -FeatureFlagEvaluations | A `FeatureFlagEvaluations` snapshot. +`FeatureFlagEvaluations` | A `FeatureFlagEvaluations` snapshot. **Examples:** @@ -668,13 +668,13 @@ Identify a group and set its properties. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`group_type` | str | The type of group (e.g., 'company', 'team'). Required - the call is dropped with a warning if it is missing or empty. | *required* -`group_key` | str | The unique identifier for the group. Required - the call is dropped with a warning if it is missing or empty. | *required* -`properties` | Optional[Dict[str, Any]] | A dictionary of properties to set on the group. | None -`timestamp` | Optional[Union[datetime, str]] | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | None -`uuid` | Optional[Union[str, UUID]] | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | None -`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this event. | None -`distinct_id` | Optional[ID_TYPES] | The distinct ID of the user performing the action. | None +`group_type` | `str` | The type of group (e.g., 'company', 'team'). Required - the call is dropped with a warning if it is missing or empty. | *required* +`group_key` | `str` | The unique identifier for the group. Required - the call is dropped with a warning if it is missing or empty. | *required* +`properties` | `Optional[Dict[str, Any]]` | A dictionary of properties to set on the group. | `None` +`timestamp` | `Optional[Union[datetime, str]]` | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | `None` +`uuid` | `Optional[Union[str, UUID]]` | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | `None` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this event. | `None` +`distinct_id` | `Optional[ID_TYPES]` | The distinct ID of the user performing the action. | `None` **Examples:** @@ -756,15 +756,15 @@ Check if a feature flag is enabled for a user. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The feature flag key. | *required* -`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None -`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None -`only_evaluate_locally` | bool | Whether to only evaluate locally. | False -`send_feature_flag_events` | bool | Whether to send feature flag events. | True -`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None -`device_id` | Optional[str] | The device ID for this request. | None +`key` | `str` | The feature flag key. | *required* +`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` +`only_evaluate_locally` | `bool` | Whether to only evaluate locally. | `False` +`send_feature_flag_events` | `bool` | Whether to send feature flag events. | `True` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` +`device_id` | `Optional[str]` | The device ID for this request. | `None` **Examples:** @@ -790,55 +790,55 @@ Initialize a new PostHog client instance. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`project_api_key` | str | PostHog project API key/token. | *required* -`host` | | PostHog host. Defaults to the US ingestion endpoint when not set. App hosts such as `https://us.posthog.com` are mapped to the corresponding ingestion host. | None -`debug` | | Enable verbose SDK logging and re-raise errors from public API methods. | False -`max_queue_size` | | Maximum number of events buffered before upload. | 10000 -`send` | | If False, queueing succeeds but events are not sent. | True -`on_error` | | Optional callback invoked by background consumers when an upload fails. Keep it short and non-blocking. Calling lifecycle methods directly is safe and deferred, but do not start another thread or task that calls `flush()`, `join()`, or `shutdown()` and then wait for it from the callback. | None -`flush_at` | | Number of queued events that triggers a batch upload. | 100 -`flush_interval` | | Maximum seconds a background consumer waits before flushing a partial batch. | 5.0 -`gzip` | | Whether to gzip event upload payloads. | False -`max_retries` | | Number of upload retries. Values below 0 are treated as 0. | 3 -`sync_mode` | | If True, send each event synchronously instead of using background worker threads. This blocks the calling thread; in asyncio applications such as FastAPI, use `AsyncPosthog` instead. | False -`timeout` | float | HTTP request timeout in seconds for event uploads. | 15 -`thread` | | Number of background consumer threads. | 1 -`poll_interval` | | Seconds between local feature flag definition refreshes. | 30 -`secret_key` | | A Personal API Key or Project Secret API Key, used to authenticate local feature flag evaluation, remote config payloads, and decrypted flag payloads. Example::
posthog.Client(project_api_key, secret_key="phx_...") | None -`personal_api_key` | | Deprecated alias for `secret_key`. Still honored for backwards compatibility; prefer `secret_key`, which also accepts a Project Secret API Key. | None -`disabled` | | If True, disable captures and API requests. Useful in tests. | False -`disable_geoip` | | Whether to disable server-side GeoIP enrichment. Defaults to True. | True -`is_server` | | Whether events are emitted from a server-side runtime. Defaults to True; set to False when using the SDK as a client/CLI so the device OS is attributed to the person normally. | True -`historical_migration` | | Mark events as historical migration imports. | False -`feature_flags_request_timeout_seconds` | float | Timeout in seconds for feature flag and remote config requests. | 3 -`feature_flags_request_max_retries` | | Number of retries for feature flag requests after network, transport, or timeout failures. Defaults to 1. Set to 0 to disable retries. | 1 -`app_host` | Optional[str] | Your PostHog app host, such as `https://posthog.example.com`. `emails` sends there. Needed only when `host` is not PostHog Cloud, for example a reverse proxy, which must not receive the secret key. | None -`email_request_timeout_seconds` | float | Timeout in seconds for each email send request. Defaults to 15. | 15 -`email_request_max_retries` | int | Number of retries for an email send after a network error, a rate limit, or an unavailable service. Defaults to 2. Set to 0 when a job runner already retries the send. | 2 -`super_properties` | | Properties merged into every captured event. | None -`enable_exception_autocapture` | | Automatically capture uncaught exceptions. | False -`log_captured_exceptions` | | Also log exceptions captured by error tracking. | False -`project_root` | | Root path used to determine in-app stack frames for captured exceptions. Defaults to the current working directory. | None -`privacy_mode` | | For AI observability, capture usage metadata without prompt inputs or outputs. | False -`enable_full_ai_capture` | | Route PostHog AI wrapper events through the dedicated AI capture endpoint and capture full AI content: skips string truncation and passes media (base64/data URIs) through unredacted. `privacy_mode` always wins. Defaults to False. | False -`before_send` | | Optional callback that can modify or drop events before upload. Return `None` to drop an event. | None -`flag_fallback_cache_url` | | Optional feature flag fallback cache URL, such as `memory://local/?ttl=300&size=10000` or a Redis URL. | None -`enable_local_evaluation` | | Whether to poll feature flag definitions for local evaluation when a personal API key is configured. | True -`flag_definition_cache_provider` | Optional[FlagDefinitionCacheProvider] | Optional external cache provider for sharing feature flag definitions across workers. | None -`capture_exception_code_variables` | | Capture local variable values on exception stack frames. | False -`capture_trace_context` | | When OpenTelemetry is installed and a valid span is active at capture time, add its trace and span IDs as `$trace_id` and `$span_id` properties to events captured with `capture()` and `capture_ai()`, so they can be correlated with backend traces. Explicit `$trace_id`/`$span_id` values passed in `properties` win. Exception events (`capture_exception`) always attach these IDs regardless of this setting. Defaults to False. | False -`traces` | Optional[dict] | Config dict for distributed tracing: `service_name`, `service_version`, `environment`, `resource_attributes`, `flush_interval` (5 s), `max_queue_size` (2048), `max_export_batch_size` (512), `max_live_spans` (10000), `max_span_age` (3600 s), `max_attributes_per_span` (128), `max_events_per_span` (128), `max_attribute_value_length` (8192). `before_span_send` is a callable, or a list run in order, that receives each finished span as a dict (`trace_id`, `span_id` and `parent_span_id` are read-only) and returns it, edited, or `None` to drop it; a hook that raises drops the span. Tracing is off until this is provided. Spans export on a background timer even with `sync_mode`; serverless handlers should call `flush()` before returning. Defaults to None. | None -`code_variables_mask_patterns` | | Variable-name patterns to mask when capturing code variables. | None -`code_variables_ignore_patterns` | | Variable-name patterns to omit when capturing code variables. | None -`code_variables_mask_url_credentials` | | Scrub credentials embedded in URLs/DSNs (e.g. `user:pass@host`) from captured code variables, regardless of the surrounding variable name. Defaults to True. | None -`code_variables_detect_secrets` | | Last-resort entropy-based detection that redacts high-entropy secret-looking values (API keys, tokens, strong passwords) sitting in innocuously-named variables, after the name and URL checks. Skips structured ids (UUIDs, ObjectIds, hashes). Defaults to True. | None -`in_app_modules` | list[str] \| None | Module/package prefixes treated as in-app frames in captured exceptions. | None -`enable_exception_autocapture_rate_limiting` | | Rate limit autocaptured exceptions client-side with a token bucket per exception type. Disabled by default. | False -`exception_autocapture_bucket_size` | | Maximum burst of autocaptured exceptions allowed per exception type (token bucket size, clamped to 0-100). | DEFAULT_BUCKET_SIZE -`exception_autocapture_refill_rate` | | Tokens restored per refill interval for each exception type's bucket. | DEFAULT_REFILL_RATE -`exception_autocapture_refill_interval_seconds` | | Seconds between token refills for autocaptured exception rate limiting. | DEFAULT_REFILL_INTERVAL_SECONDS -`capture_mode` | Optional[Union[CaptureMode, str]] | Capture wire protocol to use. Defaults to `CaptureMode.V0` (legacy `/batch/`). Set `CaptureMode.V1` (or pass the string `"v1"`) to opt into `/i/v1/analytics/events`. When omitted, the `POSTHOG_CAPTURE_MODE` env var is consulted, then `V0`. | None -`capture_compression` | Optional[Union[CaptureCompression, str]] | Request-body compression for capture-v1 uploads (ignored in V0, which uses `gzip`). `CaptureCompression.GZIP` or `DEFLATE` (or the strings `"gzip"`/`"deflate"`). When omitted, the `POSTHOG_CAPTURE_COMPRESSION` env var is consulted, then the legacy `gzip` flag, then no compression. | None +`project_api_key` | `str` | PostHog project API key/token. | *required* +`host` | | PostHog host. Defaults to the US ingestion endpoint when not set. App hosts such as `https://us.posthog.com` are mapped to the corresponding ingestion host. | `None` +`debug` | | Enable verbose SDK logging and re-raise errors from public API methods. | `False` +`max_queue_size` | | Maximum number of events buffered before upload. | `10000` +`send` | | If False, queueing succeeds but events are not sent. | `True` +`on_error` | | Optional callback invoked by background consumers when an upload fails. Keep it short and non-blocking. Calling lifecycle methods directly is safe and deferred, but do not start another thread or task that calls `flush()`, `join()`, or `shutdown()` and then wait for it from the callback. | `None` +`flush_at` | | Number of queued events that triggers a batch upload. | `100` +`flush_interval` | | Maximum seconds a background consumer waits before flushing a partial batch. | `5.0` +`gzip` | | Whether to gzip event upload payloads. | `False` +`max_retries` | | Number of upload retries. Values below 0 are treated as 0. | `3` +`sync_mode` | | If True, send each event synchronously instead of using background worker threads. This blocks the calling thread; in asyncio applications such as FastAPI, use `AsyncPosthog` instead. | `False` +`timeout` | `float` | HTTP request timeout in seconds for event uploads. | `15` +`thread` | | Number of background consumer threads. | `1` +`poll_interval` | | Seconds between local feature flag definition refreshes. | `30` +`secret_key` | | A Personal API Key or Project Secret API Key, used to authenticate local feature flag evaluation, remote config payloads, and decrypted flag payloads. Example::
posthog.Client(project_api_key, secret_key="phx_...") | `None` +`personal_api_key` | | Deprecated alias for `secret_key`. Still honored for backwards compatibility; prefer `secret_key`, which also accepts a Project Secret API Key. | `None` +`disabled` | | If True, disable captures and API requests. Useful in tests. | `False` +`disable_geoip` | | Whether to disable server-side GeoIP enrichment. Defaults to True. | `True` +`is_server` | | Whether events are emitted from a server-side runtime. Defaults to True; set to False when using the SDK as a client/CLI so the device OS is attributed to the person normally. | `True` +`historical_migration` | | Mark events as historical migration imports. | `False` +`feature_flags_request_timeout_seconds` | `float` | Timeout in seconds for feature flag and remote config requests. | `3` +`feature_flags_request_max_retries` | | Number of retries for feature flag requests after network, transport, or timeout failures. Defaults to 1. Set to 0 to disable retries. | `1` +`app_host` | `Optional[str]` | Your PostHog app host, such as `https://posthog.example.com`. `emails` sends there. Needed only when `host` is not PostHog Cloud, for example a reverse proxy, which must not receive the secret key. | `None` +`email_request_timeout_seconds` | `float` | Timeout in seconds for each email send request. Defaults to 15. | `15` +`email_request_max_retries` | `int` | Number of retries for an email send after a network error, a rate limit, or an unavailable service. Defaults to 2. Set to 0 when a job runner already retries the send. | `2` +`super_properties` | | Properties merged into every captured event. | `None` +`enable_exception_autocapture` | | Automatically capture uncaught exceptions. | `False` +`log_captured_exceptions` | | Also log exceptions captured by error tracking. | `False` +`project_root` | | Root path used to determine in-app stack frames for captured exceptions. Defaults to the current working directory. | `None` +`privacy_mode` | | For AI observability, capture usage metadata without prompt inputs or outputs. | `False` +`enable_full_ai_capture` | | Route PostHog AI wrapper events through the dedicated AI capture endpoint and capture full AI content: skips string truncation and passes media (base64/data URIs) through unredacted. `privacy_mode` always wins. Defaults to False. | `False` +`before_send` | | Optional callback that can modify or drop events before upload. Return `None` to drop an event. | `None` +`flag_fallback_cache_url` | | Optional feature flag fallback cache URL, such as `memory://local/?ttl=300&size=10000` or a Redis URL. | `None` +`enable_local_evaluation` | | Whether to poll feature flag definitions for local evaluation when a personal API key is configured. | `True` +`flag_definition_cache_provider` | `Optional[FlagDefinitionCacheProvider]` | Optional external cache provider for sharing feature flag definitions across workers. | `None` +`capture_exception_code_variables` | | Capture local variable values on exception stack frames. | `False` +`capture_trace_context` | | When OpenTelemetry is installed and a valid span is active at capture time, add its trace and span IDs as `$trace_id` and `$span_id` properties to events captured with `capture()` and `capture_ai()`, so they can be correlated with backend traces. Explicit `$trace_id`/`$span_id` values passed in `properties` win. Exception events (`capture_exception`) always attach these IDs regardless of this setting. Defaults to False. | `False` +`traces` | `Optional[dict]` | Config dict for distributed tracing: `service_name`, `service_version`, `environment`, `resource_attributes`, `flush_interval` (5 s), `max_queue_size` (2048), `max_export_batch_size` (512), `max_live_spans` (10000), `max_span_age` (3600 s), `max_attributes_per_span` (128), `max_events_per_span` (128), `max_attribute_value_length` (8192). `before_span_send` is a callable, or a list run in order, that receives each finished span as a dict (`trace_id`, `span_id` and `parent_span_id` are read-only) and returns it, edited, or `None` to drop it; a hook that raises drops the span. Tracing is off until this is provided. Spans export on a background timer even with `sync_mode`; serverless handlers should call `flush()` before returning. Defaults to None. | `None` +`code_variables_mask_patterns` | | Variable-name patterns to mask when capturing code variables. | `None` +`code_variables_ignore_patterns` | | Variable-name patterns to omit when capturing code variables. | `None` +`code_variables_mask_url_credentials` | | Scrub credentials embedded in URLs/DSNs (e.g. `user:pass@host`) from captured code variables, regardless of the surrounding variable name. Defaults to True. | `None` +`code_variables_detect_secrets` | | Last-resort entropy-based detection that redacts high-entropy secret-looking values (API keys, tokens, strong passwords) sitting in innocuously-named variables, after the name and URL checks. Skips structured ids (UUIDs, ObjectIds, hashes). Defaults to True. | `None` +`in_app_modules` | `list[str] \| None` | Module/package prefixes treated as in-app frames in captured exceptions. | `None` +`enable_exception_autocapture_rate_limiting` | | Rate limit autocaptured exceptions client-side with a token bucket per exception type. Disabled by default. | `False` +`exception_autocapture_bucket_size` | | Maximum burst of autocaptured exceptions allowed per exception type (token bucket size, clamped to 0-100). | `DEFAULT_BUCKET_SIZE` +`exception_autocapture_refill_rate` | | Tokens restored per refill interval for each exception type's bucket. | `DEFAULT_REFILL_RATE` +`exception_autocapture_refill_interval_seconds` | | Seconds between token refills for autocaptured exception rate limiting. | `DEFAULT_REFILL_INTERVAL_SECONDS` +`capture_mode` | `Optional[Union[CaptureMode, str]]` | Capture wire protocol to use. Defaults to `CaptureMode.V0` (legacy `/batch/`). Set `CaptureMode.V1` (or pass the string `"v1"`) to opt into `/i/v1/analytics/events`. When omitted, the `POSTHOG_CAPTURE_MODE` env var is consulted, then `V0`. | `None` +`capture_compression` | `Optional[Union[CaptureCompression, str]]` | Request-body compression for capture-v1 uploads (ignored in V0, which uses `gzip`). `CaptureCompression.GZIP` or `DEFLATE` (or the strings `"gzip"`/`"deflate"`). When omitted, the `POSTHOG_CAPTURE_COMPRESSION` env var is consulted, then the legacy `gzip` flag, then no compression. | `None` **Examples:** @@ -966,8 +966,8 @@ Create a new context for managing shared state. Learn more about [contexts](/doc Name | Type | Description | Default ---- | ---- | ----------- | ------- -`fresh` | | Whether to create a fresh context that doesn't inherit from parent. | False -`capture_exceptions` | Optional[bool] | Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting. | None +`fresh` | | Whether to create a fresh context that doesn't inherit from parent. | `False` +`capture_exceptions` | `Optional[bool]` | Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting. | `None` **Examples:** @@ -996,8 +996,8 @@ Decorator that creates a new context for the wrapped function using this client. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`fresh` | | Whether to create a fresh context that doesn't inherit from parent. | False -`capture_exceptions` | Optional[bool] | Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting. | None +`fresh` | | Whether to create a fresh context that doesn't inherit from parent. | `False` +`capture_exceptions` | `Optional[bool]` | Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting. | `None`
Category @@ -1018,8 +1018,8 @@ Add a tag to the current context. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`name` | str | The tag key. | *required* -`value` | Any | The tag value. | *required* +`name` | `str` | The tag key. | *required* +`value` | `Any` | The tag value. | *required*
Category @@ -1040,7 +1040,7 @@ Get all tags from the current context. Type | Description ---- | ----------- -Dict[str, Any] | Dict of all tags in the current context. +`Dict[str, Any]` | Dict of all tags in the current context.
Category @@ -1061,7 +1061,7 @@ Identify the current context with a distinct ID. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`distinct_id` | str | The distinct ID to associate with the current context and its children. | *required* +`distinct_id` | `str` | The distinct ID to associate with the current context and its children. | *required*
Category @@ -1082,7 +1082,7 @@ Set the session ID for the current context. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`session_id` | str | The session ID to associate with the current context and its children. | *required* +`session_id` | `str` | The session ID to associate with the current context and its children. | *required*
Category @@ -1103,7 +1103,7 @@ Set the device ID for the current context. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`device_id` | str | The device ID to associate with the current context and its children. | *required* +`device_id` | `str` | The device ID to associate with the current context and its children. | *required*
Category @@ -1132,13 +1132,13 @@ Get feature flag payloads for a user, preserving valid serialized JSON. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`distinct_id` | ID_TYPES | The distinct ID of the user. | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None -`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None -`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None -`flag_keys_to_evaluate` | Optional[list[str]] | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | None -`device_id` | Optional[str] | The device ID for this request. | None +`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` +`flag_keys_to_evaluate` | `Optional[list[str]]` | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | `None` +`device_id` | `Optional[str]` | The device ID for this request. | `None` **Examples:** @@ -1165,13 +1165,13 @@ Get feature flags decision. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`distinct_id` | Optional[ID_TYPES] | The distinct ID of the user. | None -`groups` | Optional[Mapping[str, Union[str, int]]] | A dictionary of group information. | None -`person_properties` | Optional[Dict[str, Any]] | A dictionary of person properties. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | A dictionary of group properties. | None -`disable_geoip` | Optional[bool] | Whether to disable GeoIP for this request. | None -`flag_keys_to_evaluate` | Optional[list[str]] | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | None -`device_id` | Optional[str] | The device ID for this request. | None +`distinct_id` | `Optional[ID_TYPES]` | The distinct ID of the user. | `None` +`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` +`flag_keys_to_evaluate` | `Optional[list[str]]` | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | `None` +`device_id` | `Optional[str]` | The device ID for this request. | `None` **Examples:** @@ -1220,7 +1220,7 @@ Get the payload for a remote config feature flag. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The remote config feature flag key. | *required* +`key` | `str` | The remote config feature flag key. | *required* **Returns:** @@ -1257,16 +1257,16 @@ Return where a locally loaded feature flag is meant to be evaluated. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The feature flag key. | *required* +`key` | `str` | The feature flag key. | *required* **Returns:** Type | Description ---- | ----------- -Optional[FeatureFlagEvaluationRuntime] | The flag's evaluation runtime, or `None` when local evaluation has -Optional[FeatureFlagEvaluationRuntime] | not loaded a definition for this key. A definition that carries no -Optional[FeatureFlagEvaluationRuntime] | runtime reports `FeatureFlagEvaluationRuntime.ALL`, the default -Optional[FeatureFlagEvaluationRuntime] | PostHog applies. +`Optional[FeatureFlagEvaluationRuntime]` | The flag's evaluation runtime, or `None` when local evaluation has +`Optional[FeatureFlagEvaluationRuntime]` | not loaded a definition for this key. A definition that carries no +`Optional[FeatureFlagEvaluationRuntime]` | runtime reports `FeatureFlagEvaluationRuntime.ALL`, the default +`Optional[FeatureFlagEvaluationRuntime]` | PostHog applies. **Examples:** @@ -1302,14 +1302,14 @@ to a browser when a backend serves flags to its own frontend. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`evaluation_runtime` | Union[FeatureFlagEvaluationRuntime, str] | The runtime to match, as a `FeatureFlagEvaluationRuntime` or its string value. | *required* +`evaluation_runtime` | `Union[FeatureFlagEvaluationRuntime, str]` | The runtime to match, as a `FeatureFlagEvaluationRuntime` or its string value. | *required* **Returns:** Type | Description ---- | ----------- -list[str] | The matching flag keys, in the order local evaluation loaded them. -list[str] | Empty when no definitions are loaded. +`list[str]` | The matching flag keys, in the order local evaluation loaded them. +`list[str]` | Empty when no definitions are loaded. **Examples:** @@ -1395,7 +1395,7 @@ Name | Description Name | Type | Description ---- | ---- | ----------- `async_capable` | | -`capture_exceptions` | Optional[bool] | +`capture_exceptions` | `Optional[bool]` | `capture_tracing_headers` | | `client` | | `extra_properties` | | @@ -1405,7 +1405,7 @@ Name | Type | Description `request_filter` | | `sync_capable` | | `tag_map` | | Compatibility alias for `properties_map`. -`trust_tracing_headers` | bool | Deprecated alias for `capture_tracing_headers`. +`trust_tracing_headers` | `bool` | Deprecated alias for `capture_tracing_headers`. Initialize the middleware with Django's next handler. @@ -1580,13 +1580,13 @@ Add PostHog request context and exception tracking to a Flask app. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`app` | Optional[Flask] | An optional Flask application. If omitted, call `init_app` later (the Flask application-factory pattern). | None -`client` | Optional[Client] | Optional destination for automatically captured exceptions. The global client is used by default. Request context still enriches events captured through any client and does not redirect capture calls. | None -`capture_exceptions` | Optional[bool] | Capture exceptions that reach Flask's unhandled exception machinery. If omitted, inherit the effective client's `enable_exception_autocapture` setting. Pass `True` or `False` to override it. | None -`request_filter` | Optional[Callable[[Request], bool]] | Optional callback receiving Flask's request object. A false return value disables both context and exception capture for that request. | None -`extra_properties` | Optional[Callable[[Request], Mapping[str, Any]]] | Optional callback returning additional event properties. This is useful for application-specific metadata such as an authenticated user's role. Values should not contain secrets or request bodies. | None -`capture_tracing_headers` | Optional[bool] | Read PostHog distinct and session ID headers as request-scoped analytics context. Defaults to True. Explicit capture IDs and event properties take precedence. Never use these values for security. | None -`trust_tracing_headers` | Optional[bool] | Deprecated alias for `capture_tracing_headers`. The capture-named option takes precedence when both are supplied. | None +`app` | `Optional[Flask]` | An optional Flask application. If omitted, call `init_app` later (the Flask application-factory pattern). | `None` +`client` | `Optional[Client]` | Optional destination for automatically captured exceptions. The global client is used by default. Request context still enriches events captured through any client and does not redirect capture calls. | `None` +`capture_exceptions` | `Optional[bool]` | Capture exceptions that reach Flask's unhandled exception machinery. If omitted, inherit the effective client's `enable_exception_autocapture` setting. Pass `True` or `False` to override it. | `None` +`request_filter` | `Optional[Callable[[Request], bool]]` | Optional callback receiving Flask's request object. A false return value disables both context and exception capture for that request. | `None` +`extra_properties` | `Optional[Callable[[Request], Mapping[str, Any]]]` | Optional callback returning additional event properties. This is useful for application-specific metadata such as an authenticated user's role. Values should not contain secrets or request bodies. | `None` +`capture_tracing_headers` | `Optional[bool]` | Read PostHog distinct and session ID headers as request-scoped analytics context. Defaults to True. Explicit capture IDs and event properties take precedence. Never use these values for security. | `None` +`trust_tracing_headers` | `Optional[bool]` | Deprecated alias for `capture_tracing_headers`. The capture-named option takes precedence when both are supplied. | `None` The integration intentionally does not capture exceptions handled by an application error handler, expected HTTP exceptions, request or response @@ -1609,7 +1609,7 @@ Name | Type | Description `client` | | `extra_properties` | | `request_filter` | | -`trust_tracing_headers` | bool | Deprecated alias for `capture_tracing_headers`. +`trust_tracing_headers` | `bool` | Deprecated alias for `capture_tracing_headers`. #### `posthog.integrations.flask.PosthogFlaskIntegration.capture_exceptions` @@ -1669,13 +1669,13 @@ Add PostHog context and automatic exception capture to an ASGI app. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`app` | _ASGIApp | The downstream ASGI application. | *required* -`client` | Optional[Client] | Optional destination for automatically captured exceptions. The global client is used by default. Request context still enriches events captured through any client and does not redirect capture calls. | None -`capture_exceptions` | Optional[bool] | Capture exceptions escaping the downstream app. If omitted, inherit the effective client's `enable_exception_autocapture` setting. Pass `True` or `False` to override it. | None -`request_filter` | Optional[_RequestFilter] | Optional sync or async callback receiving the ASGI scope. Returning `False` bypasses all instrumentation for that scope. | None -`extra_properties` | Optional[_ExtraProperties] | Optional sync or async callback receiving the ASGI scope and returning additional event properties. | None -`capture_tracing_headers` | Optional[bool] | Read PostHog distinct and session ID headers as request-scoped analytics context. Defaults to True. Explicit capture IDs and event properties take precedence. Never use these values for security. | None -`trust_tracing_headers` | Optional[bool] | Deprecated alias for `capture_tracing_headers`. The capture-named option takes precedence when both are supplied. | None +`app` | `_ASGIApp` | The downstream ASGI application. | *required* +`client` | `Optional[Client]` | Optional destination for automatically captured exceptions. The global client is used by default. Request context still enriches events captured through any client and does not redirect capture calls. | `None` +`capture_exceptions` | `Optional[bool]` | Capture exceptions escaping the downstream app. If omitted, inherit the effective client's `enable_exception_autocapture` setting. Pass `True` or `False` to override it. | `None` +`request_filter` | `Optional[_RequestFilter]` | Optional sync or async callback receiving the ASGI scope. Returning `False` bypasses all instrumentation for that scope. | `None` +`extra_properties` | `Optional[_ExtraProperties]` | Optional sync or async callback receiving the ASGI scope and returning additional event properties. | `None` +`capture_tracing_headers` | `Optional[bool]` | Read PostHog distinct and session ID headers as request-scoped analytics context. Defaults to True. Explicit capture IDs and event properties take precedence. Never use these values for security. | `None` +`trust_tracing_headers` | `Optional[bool]` | Deprecated alias for `capture_tracing_headers`. The capture-named option takes precedence when both are supplied. | `None` **Attributes:** @@ -1687,7 +1687,7 @@ Name | Type | Description `client` | | `extra_properties` | | `request_filter` | | -`trust_tracing_headers` | bool | Deprecated alias for `capture_tracing_headers`. +`trust_tracing_headers` | `bool` | Deprecated alias for `capture_tracing_headers`. #### `posthog.integrations.asgi.PosthogASGIMiddleware.app` @@ -1745,11 +1745,11 @@ Celery integration that captures task lifecycle events and exceptions. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`client` | Optional[Client] | Optional `Client` instance. When provided, all events and exceptions are captured through this client rather than the global `posthog` module. Don't skip this if using a custom flag definition cache provider, and pass the custom `Client` instance here initialized with the custom provider so fork safety for that provider is handled correctly. | None -`capture_exceptions` | bool | Whether to capture task exceptions via `capture_exception` (default `True`). | True -`capture_task_lifecycle_events` | bool | Whether to emit lifecycle events of the task such as "started", "success", "failure" etc. (default `True`). | True -`propagate_context` | bool | Whether to propagate PostHog context (distinct ID, session ID, tags) from the producer to the worker via task headers (default `True`). | True -`task_filter` | Optional[Callable[[Optional[str], dict[str, Any]], bool]] | Optional callback `(task_name, task_properties) -> bool` expected to return `False` if a given task should not be tracked. | None +`client` | `Optional[Client]` | Optional `Client` instance. When provided, all events and exceptions are captured through this client rather than the global `posthog` module. Don't skip this if using a custom flag definition cache provider, and pass the custom `Client` instance here initialized with the custom provider so fork safety for that provider is handled correctly. | `None` +`capture_exceptions` | `bool` | Whether to capture task exceptions via `capture_exception` (default `True`). | `True` +`capture_task_lifecycle_events` | `bool` | Whether to emit lifecycle events of the task such as "started", "success", "failure" etc. (default `True`). | `True` +`propagate_context` | `bool` | Whether to propagate PostHog context (distinct ID, session ID, tags) from the producer to the worker via task headers (default `True`). | `True` +`task_filter` | `Optional[Callable[[Optional[str], dict[str, Any]], bool]]` | Optional callback `(task_name, task_properties) -> bool` expected to return `False` if a given task should not be tracked. | `None` **Functions:** @@ -1853,7 +1853,7 @@ to reset to the default socket behavior. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`socket_options` | Optional[SocketOptions] | A list of `(level, option, value)` tuples accepted by urllib3/`socket.setsockopt()`, or `None` to reset defaults. | *required* +`socket_options` | `Optional[SocketOptions]` | A list of `(level, option, value)` tuples accepted by urllib3/`socket.setsockopt()`, or `None` to reset defaults. | *required*
Example @@ -2022,9 +2022,9 @@ Create a new context scope that will be active for the duration of the with bloc Name | Type | Description | Default ---- | ---- | ----------- | ------- -`fresh` | bool | Whether to start with a fresh context (default: False) | False -`capture_exceptions` | Optional[bool] | Whether to capture exceptions raised within the context. If omitted, defaults to the relevant client's exception autocapture setting. | None -`client` | Optional[Client] | Optional Posthog client instance to use for this context (default: None) | None +`fresh` | `bool` | Whether to start with a fresh context (default: False) | `False` +`capture_exceptions` | `Optional[bool]` | Whether to capture exceptions raised within the context. If omitted, defaults to the relevant client's exception autocapture setting. | `None` +`client` | `Optional[Client]` | Optional Posthog client instance to use for this context (default: None) | `None` **Examples:** @@ -2054,8 +2054,8 @@ Decorator that creates a new context for the function. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`fresh` | | Whether to start with a fresh context (default: False) | False -`capture_exceptions` | Optional[bool] | Whether to capture and track exceptions with posthog error tracking. If omitted, defaults to the global exception autocapture setting. | None +`fresh` | | Whether to start with a fresh context (default: False) | `False` +`capture_exceptions` | `Optional[bool]` | Whether to capture and track exceptions with posthog error tracking. If omitted, defaults to the global exception autocapture setting. | `None` **Examples:** @@ -2086,7 +2086,7 @@ Set the session ID for the current context. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`session_id` | str | The session ID to associate with the current context and its children | *required* +`session_id` | `str` | The session ID to associate with the current context and its children | *required* **Examples:** @@ -2115,7 +2115,7 @@ in this or child contexts with the given device ID. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`device_id` | str | The device ID to associate with the current context and its children | *required* +`device_id` | `str` | The device ID to associate with the current context and its children | *required* **Examples:** @@ -2143,7 +2143,7 @@ Identify the current context with a distinct ID. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`distinct_id` | str | The distinct ID to associate with the current context and its children | *required* +`distinct_id` | `str` | The distinct ID to associate with the current context and its children | *required* **Examples:** @@ -2171,7 +2171,7 @@ Override code-variable capture for exceptions in the current context. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`enabled` | bool | Whether exceptions captured in this context should include local variable values from stack frames. | *required* +`enabled` | `bool` | Whether exceptions captured in this context should include local variable values from stack frames. | *required*
Category @@ -2192,7 +2192,7 @@ Override code-variable mask patterns for exceptions in the current context. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`mask_patterns` | list[str] | Variable-name patterns whose values should be replaced with `***` when code variables are captured. | *required* +`mask_patterns` | `list[str]` | Variable-name patterns whose values should be replaced with `***` when code variables are captured. | *required*
Category @@ -2213,7 +2213,7 @@ Override code-variable ignore patterns for exceptions in the current context. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`ignore_patterns` | list[str] | Variable-name patterns that should be omitted entirely when code variables are captured. | *required* +`ignore_patterns` | `list[str]` | Variable-name patterns that should be omitted entirely when code variables are captured. | *required*
Category @@ -2260,8 +2260,8 @@ Add a tag to the current context. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`name` | str | The tag key | *required* -`value` | Any | The tag value | *required* +`name` | `str` | The tag key | *required* +`value` | `Any` | The tag value | *required* **Examples:** @@ -2289,7 +2289,7 @@ Get all tags from the current context. Type | Description ---- | ----------- -Dict[str, Any] | Dict of all tags in the current context +`Dict[str, Any]` | Dict of all tags in the current context
Category @@ -2310,8 +2310,8 @@ Capture anything a user does within your system. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`event` | str | The event name to specify the event | *required* -`**kwargs` | Unpack[OptionalCaptureArgs] | Optional arguments including: distinct_id: Unique identifier for the user properties: Dict of event properties timestamp: When the event occurred. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. uuid: Unique identifier for this event. If omitted, one is generated and returned. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. groups: Dict of group types and IDs flags: A FeatureFlagEvaluations snapshot from evaluate_flags(). The exact values from the snapshot are attached with no extra /flags request. send_feature_flags: Deprecated. Prefer flags=... from evaluate_flags(). When truthy, evaluates flags during capture and attaches them to the event. disable_geoip: Whether to disable GeoIP lookup | {} +`event` | `str` | The event name to specify the event | *required* +`**kwargs` | `Unpack[OptionalCaptureArgs]` | Optional arguments including: distinct_id: Unique identifier for the user properties: Dict of event properties timestamp: When the event occurred. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. uuid: Unique identifier for this event. If omitted, one is generated and returned. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. groups: Dict of group types and IDs flags: A FeatureFlagEvaluations snapshot from evaluate_flags(). The exact values from the snapshot are attached with no extra /flags request. send_feature_flags: Deprecated. Prefer flags=... from evaluate_flags(). When truthy, evaluates flags during capture and attaches them to the event. disable_geoip: Whether to disable GeoIP lookup | `{}`
Details @@ -2384,8 +2384,8 @@ redaction or truncation is applied here. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`event` | str | The event name, normally one of the `$ai_*` event names. | *required* -`**kwargs` | Unpack[OptionalCaptureArgs] | Same optional arguments as `capture()`. | {} +`event` | `str` | The event name, normally one of the `$ai_*` event names. | *required* +`**kwargs` | `Unpack[OptionalCaptureArgs]` | Same optional arguments as `capture()`. | `{}` **Examples:** @@ -2411,7 +2411,7 @@ Set properties on a user record. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`**kwargs` | Unpack[OptionalSetArgs] | Optional arguments including: distinct_id: Unique identifier for the user. Falls back to the context distinct ID; if none exists, this call does nothing. properties: Dict of person properties to set. timestamp: When the properties were set. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. uuid: Unique identifier for this operation. If omitted, one is generated and returned. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. disable_geoip: Whether to disable GeoIP lookup. | {} +`**kwargs` | `Unpack[OptionalSetArgs]` | Optional arguments including: distinct_id: Unique identifier for the user. Falls back to the context distinct ID; if none exists, this call does nothing. properties: Dict of person properties to set. timestamp: When the properties were set. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. uuid: Unique identifier for this operation. If omitted, one is generated and returned. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. disable_geoip: Whether to disable GeoIP lookup. | `{}`
Details @@ -2440,7 +2440,7 @@ Set properties on a user record, only if they do not yet exist. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`**kwargs` | Unpack[OptionalSetArgs] | Optional arguments including: distinct_id: Unique identifier for the user. Falls back to the context distinct ID; if none exists, this call does nothing. properties: Dict of person properties to set only once. timestamp: When the properties were set. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. uuid: Unique identifier for this operation. If omitted, one is generated and returned. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. disable_geoip: Whether to disable GeoIP lookup. | {} +`**kwargs` | `Unpack[OptionalSetArgs]` | Optional arguments including: distinct_id: Unique identifier for the user. Falls back to the context distinct ID; if none exists, this call does nothing. properties: Dict of person properties to set only once. timestamp: When the properties were set. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. uuid: Unique identifier for this operation. If omitted, one is generated and returned. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. disable_geoip: Whether to disable GeoIP lookup. | `{}`
Details @@ -2470,13 +2470,13 @@ Set properties on a group. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`group_type` | str | Type of your group. Required - the call is dropped with a warning if it is missing or empty. | *required* -`group_key` | str | Unique identifier of the group. Required - the call is dropped with a warning if it is missing or empty. | *required* -`properties` | Optional[Dict[str, Any]] | Properties to set on the group | None -`timestamp` | Optional[Union[datetime, str]] | Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | None -`uuid` | Optional[str] | Optional UUID for the event | None -`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup | None -`distinct_id` | Optional[ID_TYPES] | Optional distinct ID of the user performing the action | None +`group_type` | `str` | Type of your group. Required - the call is dropped with a warning if it is missing or empty. | *required* +`group_key` | `str` | Unique identifier of the group. Required - the call is dropped with a warning if it is missing or empty. | *required* +`properties` | `Optional[Dict[str, Any]]` | Properties to set on the group | `None` +`timestamp` | `Optional[Union[datetime, str]]` | Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | `None` +`uuid` | `Optional[str]` | Optional UUID for the event | `None` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup | `None` +`distinct_id` | `Optional[ID_TYPES]` | Optional distinct ID of the user performing the action | `None` **Examples:** @@ -2501,11 +2501,11 @@ Associate user behaviour before and after they e.g. register, login, or perform Name | Type | Description | Default ---- | ---- | ----------- | ------- -`previous_id` | ID_TYPES | The unique ID of the user before | *required* -`distinct_id` | str | The current unique id | *required* -`timestamp` | Optional[Union[datetime, str]] | Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | None -`uuid` | Optional[str] | Optional UUID for the event | None -`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup | None +`previous_id` | `ID_TYPES` | The unique ID of the user before | *required* +`distinct_id` | `str` | The current unique id | *required* +`timestamp` | `Optional[Union[datetime, str]]` | Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | `None` +`uuid` | `Optional[str]` | Optional UUID for the event | `None` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup | `None`
Details @@ -2534,8 +2534,8 @@ Capture exceptions that happen in your code. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`exception` | Optional[ExceptionArg] | The exception to capture. If not provided, the current exception is captured via `sys.exc_info()` | None -`**kwargs` | Unpack[OptionalCaptureArgs] | Optional capture arguments including distinct_id, properties, timestamp, uuid, groups, flags, send_feature_flags, and disable_geoip. Overriding reserved exception properties through `properties` is deprecated and will stop working in the next major version. | {} +`exception` | `Optional[ExceptionArg]` | The exception to capture. If not provided, the current exception is captured via `sys.exc_info()` | `None` +`**kwargs` | `Unpack[OptionalCaptureArgs]` | Optional capture arguments including distinct_id, properties, timestamp, uuid, groups, flags, send_feature_flags, and disable_geoip. Overriding reserved exception properties through `properties` is deprecated and will stop working in the next major version. | `{}`
Details @@ -2567,15 +2567,15 @@ Use feature flags to enable or disable features for users. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The feature flag key | *required* -`distinct_id` | ID_TYPES | The user's distinct ID | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | Groups mapping | None -`person_properties` | Optional[Dict[str, Any]] | Person properties | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties | None -`only_evaluate_locally` | bool | Whether to evaluate only locally | False -`send_feature_flag_events` | bool | Whether to send feature flag events | True -`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup | None -`device_id` | Optional[str] | Optional device ID override for experience-continuity flags | None +`key` | `str` | The feature flag key | *required* +`distinct_id` | `ID_TYPES` | The user's distinct ID | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | Groups mapping | `None` +`person_properties` | `Optional[Dict[str, Any]]` | Person properties | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties | `None` +`only_evaluate_locally` | `bool` | Whether to evaluate only locally | `False` +`send_feature_flag_events` | `bool` | Whether to send feature flag events | `True` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup | `None` +`device_id` | `Optional[str]` | Optional device ID override for experience-continuity flags | `None`
Details @@ -2606,15 +2606,15 @@ Get feature flag variant for users. Used with experiments. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The feature flag key | *required* -`distinct_id` | ID_TYPES | The user's distinct ID | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | Groups mapping from group type to group key | None -`person_properties` | Optional[Dict[str, Any]] | Person properties | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties in format { group_type_name: { group_properties } } | None -`only_evaluate_locally` | bool | Whether to evaluate only locally | False -`send_feature_flag_events` | bool | Whether to send feature flag events | True -`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup | None -`device_id` | Optional[str] | Optional device ID override for experience-continuity flags | None +`key` | `str` | The feature flag key | *required* +`distinct_id` | `ID_TYPES` | The user's distinct ID | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | Groups mapping from group type to group key | `None` +`person_properties` | `Optional[Dict[str, Any]]` | Person properties | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties in format { group_type_name: { group_properties } } | `None` +`only_evaluate_locally` | `bool` | Whether to evaluate only locally | `False` +`send_feature_flag_events` | `bool` | Whether to send feature flag events | `True` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup | `None` +`device_id` | `Optional[str]` | Optional device ID override for experience-continuity flags | `None`
Details @@ -2645,14 +2645,14 @@ Get all flags for a given user. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`distinct_id` | ID_TYPES | The user's distinct ID | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | Groups mapping | None -`person_properties` | Optional[Dict[str, Any]] | Person properties | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties | None -`only_evaluate_locally` | bool | Whether to evaluate only locally | False -`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup | None -`device_id` | Optional[str] | Optional device ID override for experience-continuity flags | None -`flag_keys_to_evaluate` | Optional[list[str]] | Optional list of flag keys to evaluate (evaluates all if None) | None +`distinct_id` | `ID_TYPES` | The user's distinct ID | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | Groups mapping | `None` +`person_properties` | `Optional[Dict[str, Any]]` | Person properties | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties | `None` +`only_evaluate_locally` | `bool` | Whether to evaluate only locally | `False` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup | `None` +`device_id` | `Optional[str]` | Optional device ID override for experience-continuity flags | `None` +`flag_keys_to_evaluate` | `Optional[list[str]]` | Optional list of flag keys to evaluate (evaluates all if None) | `None`
Details @@ -2688,15 +2688,15 @@ This method evaluates a feature flag and returns a FeatureFlagResult object cont Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The feature flag key. | *required* -`distinct_id` | ID_TYPES | The user's distinct ID. | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | Mapping of group type to group key. | None -`person_properties` | Optional[Dict[str, Any]] | Person properties to use for evaluation. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties keyed by group type. | None -`only_evaluate_locally` | bool | Whether to evaluate only locally. | False -`send_feature_flag_events` | bool | Whether to send a $feature_flag_called event. | True -`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup. | None -`device_id` | Optional[str] | Optional device ID override for experience-continuity flags. | None +`key` | `str` | The feature flag key. | *required* +`distinct_id` | `ID_TYPES` | The user's distinct ID. | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | Mapping of group type to group key. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | Person properties to use for evaluation. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties keyed by group type. | `None` +`only_evaluate_locally` | `bool` | Whether to evaluate only locally. | `False` +`send_feature_flag_events` | `bool` | Whether to send a $feature_flag_called event. | `True` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup. | `None` +`device_id` | `Optional[str]` | Optional device ID override for experience-continuity flags. | `None` Example: ```python @@ -2722,24 +2722,24 @@ Deprecated for new code. Prefer `evaluate_flags()` and Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The feature flag key. | *required* -`distinct_id` | ID_TYPES | The user's distinct ID. | *required* -`match_value` | Optional[FlagValue] | Optional flag value to use when selecting a payload. | None -`groups` | Optional[Mapping[str, Union[str, int]]] | Mapping of group type to group key. | None -`person_properties` | Optional[Dict[str, Any]] | Person properties to use for evaluation. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties keyed by group type. | None -`only_evaluate_locally` | bool | Whether to evaluate only locally. | False -`send_feature_flag_events` | bool | Whether to send a $feature_flag_called event. | True -`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup. | None -`device_id` | Optional[str] | Optional device ID override for experience-continuity flags. | None +`key` | `str` | The feature flag key. | *required* +`distinct_id` | `ID_TYPES` | The user's distinct ID. | *required* +`match_value` | `Optional[FlagValue]` | Optional flag value to use when selecting a payload. | `None` +`groups` | `Optional[Mapping[str, Union[str, int]]]` | Mapping of group type to group key. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | Person properties to use for evaluation. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties keyed by group type. | `None` +`only_evaluate_locally` | `bool` | Whether to evaluate only locally. | `False` +`send_feature_flag_events` | `bool` | Whether to send a $feature_flag_called event. | `True` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup. | `None` +`device_id` | `Optional[str]` | Optional device ID override for experience-continuity flags. | `None` **Returns:** Type | Description ---- | ----------- -Optional[object] | The payload associated with the matched feature flag value, or None. -Optional[object] | This function returns the payload only, not the FeatureFlagResult wrapper -Optional[object] | used internally to compute it. +`Optional[object]` | The payload associated with the matched feature flag value, or None. +`Optional[object]` | This function returns the payload only, not the FeatureFlagResult wrapper +`Optional[object]` | used internally to compute it. ### `posthog.get_remote_config_payload` @@ -2753,7 +2753,7 @@ Get the payload for a remote config feature flag. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The key of the feature flag | *required* +`key` | `str` | The key of the feature flag | *required* **Returns:** @@ -2780,20 +2780,20 @@ Get all feature flag values and payloads for a user. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`distinct_id` | ID_TYPES | The user's distinct ID. | *required* -`groups` | Optional[Mapping[str, Union[str, int]]] | Mapping of group type to group key. | None -`person_properties` | Optional[Dict[str, Any]] | Person properties to use for evaluation. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties keyed by group type. | None -`only_evaluate_locally` | bool | Whether to evaluate only locally. | False -`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup. | None -`device_id` | Optional[str] | Optional device ID override for experience-continuity flags. | None -`flag_keys_to_evaluate` | Optional[list[str]] | Optional list of flag keys to evaluate. Evaluates all flags when omitted. | None +`distinct_id` | `ID_TYPES` | The user's distinct ID. | *required* +`groups` | `Optional[Mapping[str, Union[str, int]]]` | Mapping of group type to group key. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | Person properties to use for evaluation. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties keyed by group type. | `None` +`only_evaluate_locally` | `bool` | Whether to evaluate only locally. | `False` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup. | `None` +`device_id` | `Optional[str]` | Optional device ID override for experience-continuity flags. | `None` +`flag_keys_to_evaluate` | `Optional[list[str]]` | Optional list of flag keys to evaluate. Evaluates all flags when omitted. | `None` **Returns:** Type | Description ---- | ----------- -FlagsAndPayloads | A dict with `featureFlags` and `featureFlagPayloads` entries. +`FlagsAndPayloads` | A dict with `featureFlags` and `featureFlagPayloads` entries. ### `posthog.evaluate_flags` @@ -2814,14 +2814,14 @@ a single `/flags` request per incoming request. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`distinct_id` | Optional[ID_TYPES] | The user's distinct ID. If `None`, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot. | None -`groups` | Optional[Mapping[str, Union[str, int]]] | Mapping of group type to group key. | None -`person_properties` | Optional[Dict[str, Any]] | Person properties to use for evaluation. | None -`group_properties` | Optional[Dict[str, Dict[str, Any]]] | Group properties keyed by group type. | None -`only_evaluate_locally` | bool | If `True`, never fall back to remote evaluation and omit flags that cannot be evaluated locally. The snapshot's `unresolved_flags` lists them with a reason. | False -`disable_geoip` | Optional[bool] | Whether to disable GeoIP lookup. | None -`flag_keys` | Optional[list[str]] | Optional list that scopes local evaluation, the underlying `/flags` request, and the returned snapshot. When omitted or `None`, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per `evaluate_flags` call unless `only_evaluate_locally` is `True`. If the server also does not know the key, it is omitted from the snapshot. | None -`device_id` | Optional[str] | Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes. | None +`distinct_id` | `Optional[ID_TYPES]` | The user's distinct ID. If `None`, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot. | `None` +`groups` | `Optional[Mapping[str, Union[str, int]]]` | Mapping of group type to group key. | `None` +`person_properties` | `Optional[Dict[str, Any]]` | Person properties to use for evaluation. | `None` +`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties keyed by group type. | `None` +`only_evaluate_locally` | `bool` | If `True`, never fall back to remote evaluation and omit flags that cannot be evaluated locally. The snapshot's `unresolved_flags` lists them with a reason. | `False` +`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup. | `None` +`flag_keys` | `Optional[list[str]]` | Optional list that scopes local evaluation, the underlying `/flags` request, and the returned snapshot. When omitted or `None`, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per `evaluate_flags` call unless `only_evaluate_locally` is `True`. If the server also does not know the key, it is omitted from the snapshot. | `None` +`device_id` | `Optional[str]` | Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes. | `None` **Examples:** @@ -2956,7 +2956,7 @@ rather than blocking the worker that invoked the callback. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`timeout_seconds` | Optional[float] | Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass `None` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently, waiting up to this many seconds for an in-flight flush before starting a fresh drain budget. A lock wait timeout leaves the in-flight flush to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. The last span request is not cut short and is bounded by the client's `timeout` in seconds, so this is not a strict wall-clock limit for the whole call. With `None`, a retriable span failure is left for a later flush rather than retried within this call. | 10 +`timeout_seconds` | `Optional[float]` | Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass `None` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently, waiting up to this many seconds for an in-flight flush before starting a fresh drain budget. A lock wait timeout leaves the in-flight flush to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. The last span request is not cut short and is bounded by the client's `timeout` in seconds, so this is not a strict wall-clock limit for the whole call. With `None`, a retriable span failure is left for a later flush rather than retried within this call. | `10` **Examples:** @@ -3004,12 +3004,12 @@ a usable handle, even when tracing is off, so calling code never branches. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`name` | str | A low-cardinality operation name, e.g. `GET /users/:id`. Variable values belong in attributes, not the name. | *required* -`kind` | Optional[str] | `internal` (default), `server`, `client`, `producer` or `consumer`. | None -`attributes` | Optional[Mapping[str, Any]] | Initial attributes. | None -`parent` | Union[Span, str, None] | A span handle, or an inbound W3C `traceparent` header value to continue a remote trace. Defaults to the active span. | None -`tracestate` | Optional[str] | The inbound `tracestate` header accompanying a `traceparent` string `parent`; preserved and propagated. | None -`start_time` | Union[datetime, float, None] | A `datetime` or epoch seconds, to backdate the span. | None +`name` | `str` | A low-cardinality operation name, e.g. `GET /users/:id`. Variable values belong in attributes, not the name. | *required* +`kind` | `Optional[str]` | `internal` (default), `server`, `client`, `producer` or `consumer`. | `None` +`attributes` | `Optional[Mapping[str, Any]]` | Initial attributes. | `None` +`parent` | `Union[Span, str, None]` | A span handle, or an inbound W3C `traceparent` header value to continue a remote trace. Defaults to the active span. | `None` +`tracestate` | `Optional[str]` | The inbound `tracestate` header accompanying a `traceparent` string `parent`; preserved and propagated. | `None` +`start_time` | `Union[datetime, float, None]` | A `datetime` or epoch seconds, to backdate the span. | `None` **Examples:** @@ -3095,15 +3095,15 @@ Most applications should either instantiate `Posthog` directly or set Type | Description ---- | ----------- -Client | The global `Client` instance. If both `api_key` and -Client | `project_api_key` are missing or blank, the client is disabled and -Client | module-level calls become no-ops. +`Client` | The global `Client` instance. If both `api_key` and +`Client` | `project_api_key` are missing or blank, the client is disabled and +`Client` | module-level calls become no-ops. ## Types ### `OptionalCaptureArgs` -Bases: TypedDict +Bases: `TypedDict` Optional arguments for the capture method. @@ -3124,14 +3124,14 @@ Name | Type | Description | Default Name | Type | Description ---- | ---- | ----------- -`disable_geoip` | NotRequired[Optional[bool]] | -`distinct_id` | NotRequired[Optional[ID_TYPES]] | -`flags` | NotRequired[Optional[FeatureFlagEvaluations]] | -`groups` | NotRequired[Optional[Dict[str, str]]] | -`properties` | NotRequired[Optional[Dict[str, Any]]] | -`send_feature_flags` | NotRequired[Optional[Union[bool, SendFeatureFlagsOptions]]] | -`timestamp` | NotRequired[Optional[Union[datetime, str]]] | -`uuid` | NotRequired[Optional[Union[str, UUID]]] | +`disable_geoip` | `NotRequired[Optional[bool]]` | +`distinct_id` | `NotRequired[Optional[ID_TYPES]]` | +`flags` | `NotRequired[Optional[FeatureFlagEvaluations]]` | +`groups` | `NotRequired[Optional[Dict[str, str]]]` | +`properties` | `NotRequired[Optional[Dict[str, Any]]]` | +`send_feature_flags` | `NotRequired[Optional[Union[bool, SendFeatureFlagsOptions]]]` | +`timestamp` | `NotRequired[Optional[Union[datetime, str]]]` | +`uuid` | `NotRequired[Optional[Union[str, UUID]]]` | #### `posthog.args.OptionalCaptureArgs.disable_geoip` @@ -3219,8 +3219,8 @@ Name | Description Name | Type | Description ---- | ---- | ----------- -`keys` | List[str] | Return the flag keys that are part of this evaluation. -`unresolved_flags` | Mapping[str, UnresolvedFlagReason] | Return a read-only mapping of flag key to the reason local evaluation +`keys` | `List[str]` | Return the flag keys that are part of this evaluation. +`unresolved_flags` | `Mapping[str, UnresolvedFlagReason]` | Return a read-only mapping of flag key to the reason local evaluation Internal — instances are created by the SDK via `Client.evaluate_flags()`. @@ -3262,9 +3262,9 @@ first access per (distinct_id, flag, value) tuple, deduped via the SDK's cache. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The feature flag key. | *required* -`default_value` | bool | Returned when the flag has no value in this evaluation — the key was not part of the evaluated set, or the evaluation itself came back empty (failed `/flags` request, quota limit, or no resolvable `distinct_id`). Defaults to `False`. | False -`send_feature_flag_events` | bool | Whether this read sends `$feature_flag_called`. When `False`, no event is sent and the dedupe cache is left alone, but the key still counts as accessed for `only_accessed`. | True +`key` | `str` | The feature flag key. | *required* +`default_value` | `bool` | Returned when the flag has no value in this evaluation — the key was not part of the evaluated set, or the evaluation itself came back empty (failed `/flags` request, quota limit, or no resolvable `distinct_id`). Defaults to `False`. | `False` +`send_feature_flag_events` | `bool` | Whether this read sends `$feature_flag_called`. When `False`, no event is sent and the dedupe cache is left alone, but the key still counts as accessed for `only_accessed`. | `True` A flag that has a value always wins over `default_value`, including a disabled flag, which returns `False` even when `default_value=True`. @@ -3328,11 +3328,11 @@ The result of calling a feature flag which includes the flag result, variant, an Name | Type | Description ---- | ---- | ----------- -`key` | str | The unique identifier of the feature flag. -`enabled` | bool | Whether the feature flag is enabled for the current context. -`variant` | Optional[str] | The variant value if the flag is enabled and has variants, None otherwise. -`payload` | Optional[Any] | Additional data associated with the feature flag, if any. -`reason` | Optional[str] | A description of why the flag was enabled or disabled, if available. +`key` | `str` | The unique identifier of the feature flag. +`enabled` | `bool` | Whether the feature flag is enabled for the current context. +`variant` | `Optional[str]` | The variant value if the flag is enabled and has variants, None otherwise. +`payload` | `Optional[Any]` | Additional data associated with the feature flag, if any. +`reason` | `Optional[str]` | A description of why the flag was enabled or disabled, if available. **Functions:** @@ -3360,14 +3360,14 @@ Create a FeatureFlagResult from a FeatureFlag object. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`details` | Union[FeatureFlag, None] | The FeatureFlag object to convert. | *required* -`override_match_value` | Optional[FlagValue] | If provided, this value will be used to populate the enabled and variant fields instead of the values from the FeatureFlag. | None +`details` | `Union[FeatureFlag, None]` | The FeatureFlag object to convert. | *required* +`override_match_value` | `Optional[FlagValue]` | If provided, this value will be used to populate the enabled and variant fields instead of the values from the FeatureFlag. | `None` **Returns:** Type | Description ---- | ----------- -FeatureFlagResult \| None | FeatureFlagResult | None: A new FeatureFlagResult instance, or None if details is None. +`FeatureFlagResult \| None` | FeatureFlagResult | None: A new FeatureFlagResult instance, or None if details is None. #### `posthog.types.FeatureFlagResult.from_value_and_payload` @@ -3381,15 +3381,15 @@ Creates a FeatureFlagResult from a flag value and payload. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`key` | str | The unique identifier of the feature flag. | *required* -`value` | Union[FlagValue, None] | The value of the flag (string variant or boolean). | *required* -`payload` | Any | Additional data associated with the feature flag. | *required* +`key` | `str` | The unique identifier of the feature flag. | *required* +`value` | `Union[FlagValue, None]` | The value of the flag (string variant or boolean). | *required* +`payload` | `Any` | Additional data associated with the feature flag. | *required* **Returns:** Type | Description ---- | ----------- -Union[FeatureFlagResult, None] | Union[FeatureFlagResult, None]: A new FeatureFlagResult instance, or None if value is None. +`Union[FeatureFlagResult, None]` | Union[FeatureFlagResult, None]: A new FeatureFlagResult instance, or None if value is None. #### `posthog.types.FeatureFlagResult.get_value` @@ -3404,7 +3404,7 @@ This is the value we report as `$feature_flag_response` in the `$feature_flag_ca Name | Type | Description ---- | ---- | ----------- -`FlagValue` | FlagValue | Either a string variant or boolean value representing the flag's state. +`FlagValue` | `FlagValue` | Either a string variant or boolean value representing the flag's state. #### `posthog.types.FeatureFlagResult.key` @@ -3445,11 +3445,11 @@ responses before `/flags?v=4` carried it, which the legacy getters return. Name | Type | Description ---- | ---- | ----------- -`key` | str | Feature flag key. -`enabled` | bool | Whether the flag is enabled for the evaluated user or group. -`variant` | Optional[str] | Variant key for multivariate flags, otherwise `None`. -`reason` | Optional[FlagReason] | Optional reason metadata explaining the result. -`metadata` | Union[FlagMetadata, LegacyFlagMetadata] | Payload and other metadata returned by the API. +`key` | `str` | Feature flag key. +`enabled` | `bool` | Whether the flag is enabled for the evaluated user or group. +`variant` | `Optional[str]` | Variant key for multivariate flags, otherwise `None`. +`reason` | `Optional[FlagReason]` | Optional reason metadata explaining the result. +`metadata` | `Union[FlagMetadata, LegacyFlagMetadata]` | Payload and other metadata returned by the API. **Functions:** @@ -3519,7 +3519,7 @@ variant: Optional[str] ### `FlagsAndPayloads` -Bases: TypedDict +Bases: `TypedDict` Feature flag values and payloads keyed by feature flag key. @@ -3527,8 +3527,8 @@ Feature flag values and payloads keyed by feature flag key. Name | Type | Description ---- | ---- | ----------- -`featureFlags` | Optional[dict[str, FlagValue]] | Each flag's value, keyed by flag key. -`featureFlagPayloads` | Optional[dict[str, Any]] | Each flag's payload, keyed by flag key. +`featureFlags` | `Optional[dict[str, FlagValue]]` | Each flag's value, keyed by flag key. +`featureFlagPayloads` | `Optional[dict[str, Any]]` | Each flag's payload, keyed by flag key. #### `posthog.types.FlagsAndPayloads.featureFlagPayloads` @@ -3544,7 +3544,7 @@ featureFlags: Optional[dict[str, FlagValue]] ### `UnresolvedFlagReason` -Bases: str, Enum +Bases: `str`, `Enum` Why local evaluation left a flag without a value. @@ -3607,7 +3607,7 @@ UNSUPPORTED_DEFINITION = 'unsupported_definition' InconclusiveMatchError(*args, reason: Optional[UnresolvedFlagReason] = None) ``` -Bases: Exception +Bases: `Exception` Raised when local evaluation can't determine a flag's value. `reason` says why, as reported by `FeatureFlagEvaluations.unresolved_flags`. @@ -3616,7 +3616,7 @@ why, as reported by `FeatureFlagEvaluations.unresolved_flags`. Name | Type | Description ---- | ---- | ----------- -`reason` | UnresolvedFlagReason | Why local evaluation couldn't resolve the flag. +`reason` | `UnresolvedFlagReason` | Why local evaluation couldn't resolve the flag. #### `posthog.feature_flags.InconclusiveMatchError.reason` @@ -3630,7 +3630,7 @@ reason: UnresolvedFlagReason = UnresolvedFlagReason.UNSUPPORTED_DEFINITION RequiresServerEvaluation(*args, reason: Optional[UnresolvedFlagReason] = None) ``` -Bases: Exception +Bases: `Exception` Raised when feature flag evaluation requires server-side data that is not available locally (e.g., static cohorts). @@ -3642,7 +3642,7 @@ InconclusiveMatchError which allows trying other conditions. Name | Type | Description ---- | ---- | ----------- -`reason` | UnresolvedFlagReason | Why the flag needs server-side evaluation. +`reason` | `UnresolvedFlagReason` | Why the flag needs server-side evaluation. #### `posthog.feature_flags.RequiresServerEvaluation.reason` @@ -3834,7 +3834,7 @@ span.update_name(f"{request.method} {route.pattern}") Emails(secret_key: Optional[str], *, host: Optional[str] = None, timeout: float = 15, max_retries: int = 2, disabled: bool = False, session: Optional[requests.Session] = None) -> None ``` -Bases: _EmailsBase +Bases: `_EmailsBase` Sends transactional email from PostHog email templates. @@ -3892,20 +3892,20 @@ Read what happened to an email that `send` queued. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`message_id` | Union[str, UUID] | The `id` of the `SendEmailResult` that `send` returned. | *required* +`message_id` | `Union[str, UUID]` | The `id` of the `SendEmailResult` that `send` returned. | *required* **Returns:** Type | Description ---- | ----------- -EmailStatus | The email's latest status, for 30 days after the send. +`EmailStatus` | The email's latest status, for 30 days after the send. **Raises:** Type | Description ---- | ----------- -EmailError | PostHog did not answer with a status. `code` is `not_found` for an unknown ID, an email older than 30 days, or for a few seconds after the send. -ValueError | `message_id` is not a message ID. +`EmailError` | PostHog did not answer with a status. `code` is `not_found` for an unknown ID, an email older than 30 days, or for a few seconds after the send. +`ValueError` | `message_id` is not a message ID. **Examples:** @@ -3946,26 +3946,26 @@ Queue one email from a transactional email template. Name | Type | Description | Default ---- | ---- | ----------- | ------- -`to` | str | One recipient email address. | *required* -`template_id` | Union[str, UUID] | The UUID of a template made transactional in PostHog. | *required* -`template_data` | Optional[Mapping[str, Any]] | Values for the template's `{{ variables.* }}` placeholders. Values are encoded the way capture encodes properties. | None -`distinct_id` | Optional[str] | The recipient's distinct ID in your product. The email's events go to this person. Without it, they go to a person whose distinct ID is the email address. | None -`idempotency_key` | Optional[str] | 1 to 255 visible ASCII characters. A send with a key that PostHog has already accepted queues nothing and returns the first send's result. Without it, the client makes a key that covers its own retries. Pass a key derived from what the email is for, such as `welcome:user_123`, to cover retries outside this call, too. | None -`max_retries` | Optional[int] | Overrides the client's maximum number of retries for this send. `0` turns retries off. | None +`to` | `str` | One recipient email address. | *required* +`template_id` | `Union[str, UUID]` | The UUID of a template made transactional in PostHog. | *required* +`template_data` | `Optional[Mapping[str, Any]]` | Values for the template's `{{ variables.* }}` placeholders. Values are encoded the way capture encodes properties. | `None` +`distinct_id` | `Optional[str]` | The recipient's distinct ID in your product. The email's events go to this person. Without it, they go to a person whose distinct ID is the email address. | `None` +`idempotency_key` | `Optional[str]` | 1 to 255 visible ASCII characters. A send with a key that PostHog has already accepted queues nothing and returns the first send's result. Without it, the client makes a key that covers its own retries. Pass a key derived from what the email is for, such as `welcome:user_123`, to cover retries outside this call, too. | `None` +`max_retries` | `Optional[int]` | Overrides the client's maximum number of retries for this send. `0` turns retries off. | `None` **Returns:** Type | Description ---- | ----------- -SendEmailResult | The queued email. A result means PostHog queued the email, not that -SendEmailResult | it was delivered. +`SendEmailResult` | The queued email. A result means PostHog queued the email, not that +`SendEmailResult` | it was delivered. **Raises:** Type | Description ---- | ----------- -EmailError | PostHog did not queue the email, or the client could not tell whether it did. -(TypeError, ValueError) | An argument has the wrong type or format. +`EmailError` | PostHog did not queue the email, or the client could not tell whether it did. +`(TypeError, ValueError)` | An argument has the wrong type or format. **Examples:** @@ -3991,7 +3991,7 @@ timeout = timeout EmailError(*, name: str, code: str, message: str, status_code: Optional[int] = None, why: Optional[str] = None, fix: Optional[str] = None, docs_url: Optional[str] = None, field: Optional[str] = None, retry_after: Optional[float] = None) -> None ``` -Bases: Exception +Bases: `Exception` PostHog did not queue the email, or the client could not tell whether it did. @@ -4079,9 +4079,9 @@ The queued email. `id` is its message ID. Name | Type | Description ---- | ---- | ----------- -`id` | str | The email's message ID. -`replayed` | bool | `True` when the response says PostHog replayed an earlier send for the same `idempotency_key` instead of sending again. -`warnings` | tuple[EmailWarning, ...] | Warnings PostHog returned about the request. +`id` | `str` | The email's message ID. +`replayed` | `bool` | `True` when the response says PostHog replayed an earlier send for the same `idempotency_key` instead of sending again. +`warnings` | `tuple[EmailWarning, ...]` | Warnings PostHog returned about the request. #### `posthog.emails.SendEmailResult.id` From 22efef7ef15864db59969d8eea2c332d572bea4f Mon Sep 17 00:00:00 2001 From: "Vincent (Wen Yu) Ge" Date: Wed, 4 Nov 2026 17:59:10 -0500 Subject: [PATCH 8/8] docs: put setup, capture and identify first, and drop the generated pages Co-Authored-By: Claude Opus 5.5 --- bin/docs_scripts/generate_markdown.py | 5 +- references/posthog-python-7.md | 4102 ---------------------- references/posthog-python-8.md | 4363 ------------------------ references/posthog-python-migration.md | 269 -- references/posthog-python-reference.md | 4102 ---------------------- 5 files changed, 4 insertions(+), 12837 deletions(-) delete mode 100644 references/posthog-python-7.md delete mode 100644 references/posthog-python-8.md delete mode 100644 references/posthog-python-migration.md delete mode 100644 references/posthog-python-reference.md diff --git a/bin/docs_scripts/generate_markdown.py b/bin/docs_scripts/generate_markdown.py index 6667fd4f5..f17036812 100644 --- a/bin/docs_scripts/generate_markdown.py +++ b/bin/docs_scripts/generate_markdown.py @@ -22,6 +22,7 @@ UTILS = """integrations.django.PosthogContextMiddleware integrations.flask.PosthogFlaskIntegration integrations.asgi.PosthogASGIMiddleware integrations.celery.PosthogCeleryIntegration request.set_socket_options request.enable_keep_alive request.disable_connection_reuse""".split() +FIRST = ["setup", "capture", "identify"] TAG = re.compile(r"^[ \t]*posthog-capability:[ \t]*([a-z0-9-]+)[ \t]*$\n?", re.M) ROLE = re.compile( r":(?:py:)?(?:meth|func|class|attr|obj|data|exc|mod|const):`~?([^`]+)`" @@ -83,7 +84,9 @@ def capabilities(checkout: str) -> dict[str, str]: s: " ".join(acronyms.get(w.lower(), w.lower()) for w in n.split()) for n, s in rows } - return {s: t[0].upper() + t[1:] for s, t in titles.items()} + # Setup, capture and identify come first; the rest follow the README. + order = [*FIRST, *(s for s in titles if s not in FIRST)] + return {s: titles[s][0].upper() + titles[s][1:] for s in order if s in titles} def has(pkg, path: str) -> bool: diff --git a/references/posthog-python-7.md b/references/posthog-python-7.md deleted file mode 100644 index bee1b6cc1..000000000 --- a/references/posthog-python-7.md +++ /dev/null @@ -1,4102 +0,0 @@ ---- -title: "PostHog Python SDK 7.x" -sidebar: Docs -showTitle: true -contentMaxWidthClass: max-w-full -platformLogo: python -tableOfContents: - - value: "Getting started" - url: getting-started - depth: 0 - - value: "Traces" - url: traces - depth: 0 - - value: "Alias" - url: alias - depth: 0 - - value: "Capture" - url: capture - depth: 0 - - value: "Capture AI" - url: capture-ai - depth: 0 - - value: "Capture exception" - url: capture-exception - depth: 0 - - value: "Emails" - url: emails - depth: 0 - - value: "Flush" - url: flush - depth: 0 - - value: "Get feature flag" - url: get-feature-flag - depth: 0 - - value: "Get feature flag payload" - url: get-feature-flag-payload - depth: 0 - - value: "Get feature flag result" - url: get-feature-flag-result - depth: 0 - - value: "Get feature flags" - url: get-feature-flags - depth: 0 - - value: "Get feature flags and payloads" - url: get-feature-flags-and-payloads - depth: 0 - - value: "Evaluate flags" - url: evaluate-flags - depth: 0 - - value: "Group identify" - url: group-identify - depth: 0 - - value: "Identify" - url: identify - depth: 0 - - value: "Is feature enabled" - url: is-feature-enabled - depth: 0 - - value: "Setup" - url: setup - depth: 0 - - value: "Shutdown" - url: shutdown - depth: 0 - - value: "Flag definition loader" - url: flag-definition-loader - depth: 0 - - value: "SDK utils" - url: sdk-utils - depth: 0 - - value: "AsyncClient" - url: asyncclient - depth: 0 - - value: "Module functions" - url: module-functions - depth: 0 - - value: "Types" - url: types - depth: 0 ---- - -## Getting started - - -The Python SDK captures events, evaluates feature flags and reports errors from Python 3.10 or later. - -### Install - -```bash -pip install posthog -``` - -### Initialize - -Create one client for your app with your project token and host. Your project token and host are in your [project settings](https://app.posthog.com/project/settings). Keep the token in an environment variable rather than in code. - -```python -from posthog import Posthog - -posthog = Posthog("", host="") -``` - -### Capture an event - -```python -posthog.capture("user_signed_up", distinct_id="user_123", properties={"plan": "free"}) -``` - -`capture` queues the event and returns right away. A background thread sends it. - -### Shut down - -Call `shutdown()` before your process exits, so queued events are sent. - -```python -posthog.shutdown() -``` - -In an asyncio app, use `AsyncPosthog` as an async context manager instead. See [AsyncClient](#asyncclient). - -## Traces - -### `Client.start_span` - -```python -start_span(name: str, *, kind: Optional[str] = None, attributes: Optional[Mapping[str, Any]] = None, parent: Union[Span, str, None] = None, tracestate: Optional[str] = None, start_time: Union[datetime, float, None] = None) -> Span -``` - -Start a span for distributed tracing. Alpha. - -Returns a span handle. Use it as a context manager to make it the active -span for the block and end it on exit (recording a raised exception on -the way out); or call `end()` yourself for a span that cannot wrap a -block. Spans started inside the block nest under it automatically. -Always returns a usable handle, even when tracing is off, so calling -code never branches. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`name` | `str` | A low-cardinality operation name, e.g. `GET /users/:id`. Variable values belong in attributes, not the name. | *required* -`kind` | `Optional[str]` | `internal` (default), `server`, `client`, `producer` or `consumer`. | `None` -`attributes` | `Optional[Mapping[str, Any]]` | Initial attributes. | `None` -`parent` | `Union[Span, str, None]` | A span handle, or an inbound W3C `traceparent` header value to continue a remote trace. Defaults to the active span. A forked child starts with no active span; pass the parent span to continue a trace across a fork. | `None` -`tracestate` | `Optional[str]` | The inbound `tracestate` header accompanying a `traceparent` string `parent`; preserved and propagated. | `None` -`start_time` | `Union[datetime, float, None]` | A `datetime` or epoch seconds, to backdate the span. | `None` - -**Examples:** - -```python -posthog = Posthog("", traces={"service_name": "checkout-api"}) - -with posthog.start_span("POST /checkout", parent=request.headers.get("traceparent")) as span: - span.set_attribute("plan", user.plan) - with posthog.start_span("db.query", kind="client"): - ... - outgoing_headers = {"traceparent": span.traceparent()} -``` - -### `Client.get_active_span` - -```python -get_active_span() -> Optional[Span] -``` - -The span that is active in the current context, or `None`. Alpha. - -Only entering a span (`with posthog.start_span(...) as span:`) makes -it active; a span started manually is not. Use it to propagate the -trace to the next service: `span.traceparent()` is the header value. - -**Examples:** - -```python -span = posthog.get_active_span() -if span is not None: - headers["traceparent"] = span.traceparent() -``` - -## Alias - -### `Client.alias` - -```python -alias(previous_id: ID_TYPES, distinct_id: Optional[str], timestamp: Optional[Union[datetime, str]] = None, uuid: Optional[str] = None, disable_geoip: Optional[bool] = None) -> Optional[str] -``` - -Create an alias between two distinct IDs. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`previous_id` | `ID_TYPES` | The previous distinct ID. Required - the call is dropped with a warning if it is missing or empty. | *required* -`distinct_id` | `Optional[str]` | The new distinct ID to alias to. Falls back to the context distinct ID; the call is dropped with a warning if neither is available. | *required* -`timestamp` | `Optional[Union[datetime, str]]` | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | `None` -`uuid` | `Optional[str]` | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | `None` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this event. | `None` - -**Examples:** - -```python -posthog.alias(previous_id='distinct_id', distinct_id='alias_id') -``` - -Note: This method will not raise exceptions. Errors are logged. - -## Capture - -### `Client.capture` - -```python -capture(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] -``` - -Captures an event manually. [Learn about capture best practices](https://posthog.com/docs/product-analytics/capture-events) - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`event` | `str` | The event name to capture. | *required* -`distinct_id` | | The distinct ID of the user. | -`properties` | | A dictionary of properties to include with the event. | -`timestamp` | | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | -`uuid` | | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | -`groups` | | A dictionary of group information. | -`flags` | | A FeatureFlagEvaluations snapshot from evaluate_flags(). The exact values from the snapshot are attached with no extra /flags request. | -`send_feature_flags` | | Deprecated. Prefer flags=... from evaluate_flags(). When truthy, evaluates flags during capture and attaches them to the event. | -`disable_geoip` | | Whether to disable GeoIP for this event. | - -**Examples:** - -```python -# Anonymous event -posthog.capture('some-anon-event') -``` -```python -# Context usage -from posthog import identify_context, new_context -with new_context(): - identify_context('distinct_id_of_the_user') - posthog.capture('user_signed_up') - posthog.capture('user_logged_in') - posthog.capture('some-custom-action', distinct_id='distinct_id_of_the_user') -``` -```python -# Set event properties -posthog.capture( - "user_signed_up", - distinct_id="distinct_id_of_the_user", - properties={ - "login_type": "email", - "is_free_trial": "true" - } -) -``` -```python -# Page view event -posthog.capture('$pageview', distinct_id="distinct_id_of_the_user", properties={'$current_url': 'https://example.com'}) -``` - -## Capture AI - -### `Client.capture_ai` - -```python -capture_ai(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] -``` - -Capture an AI event on the dedicated AI capture endpoint. - -Beta: the signature is stable; operational limits (per-event size -cap, batching, endpoint) may change without notice. - -Takes the same arguments and returns the same value as `capture()`: -the event UUID, or None when the event was not admitted (disabled -client, or dropped by `before_send`). The event is queued on an -isolated AI lane with its own consumer pool and a higher per-event -size cap, posting to the dedicated AI ingestion endpoint. The payload -is sent as given — no redaction or truncation is applied here. - -## Capture exception - -### `Client.capture_exception` - -```python -capture_exception(exception: Optional[ExceptionArg], **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] -``` - -Capture an exception for error tracking. - -When OpenTelemetry is installed and a valid span is active, its trace and -span IDs are added as `$trace_id` and `$span_id` event properties. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`exception` | `Optional[ExceptionArg]` | The exception to capture. | *required* -`distinct_id` | | The distinct ID of the user. | -`properties` | | A dictionary of additional properties. Overriding reserved exception properties is deprecated and will stop working in the next major version. | -`flags` | | A `FeatureFlagEvaluations` snapshot from `evaluate_flags()`. Attaches those exact flag values to the captured `$exception` event. | -`send_feature_flags` | | Deprecated. Pass `flags` from `evaluate_flags()` instead. | -`disable_geoip` | | Whether to disable GeoIP for this event. | - -**Examples:** - -```python -try: - # Some code that might fail - pass -except Exception as e: - posthog.capture_exception(e, 'user_distinct_id', properties=additional_properties) -``` - -## Emails - -### `Client.emails` - -```python -emails: Emails -``` - -Send transactional email from PostHog email templates, and read the -status of each email sent with `get`. - -Needs `secret_key`, a project secret API key with the `hog_flow:write` -scope. Sends go to `app_host`, or to the PostHog Cloud app host of -`host`. `email_request_timeout_seconds` and `email_request_max_retries` -configure each send. A disabled or shut-down client sends nothing. See -`posthog.emails` for setup. - -**Examples:** - -```python -client.emails.send( - to="user@example.com", - template_id="0190f6d2-7c1e-7b3a-9f5e-1c2d3e4f5a6b", - template_data={"name": "Ada"}, - distinct_id="user_123", -) -``` - -## Flush - -### `Client.flush` - -```python -flush(timeout_seconds: Optional[float] = 10) -> None -``` - -Attempt immediate delivery of queued events, ended spans and autocaptured metrics, keeping the client reusable. - -For a client shared across requests or serverless invocations (including -the module-level client), call `flush()` before a short-lived invocation -returns. Use `shutdown()` instead only for final process cleanup or when -disposing of a client that will not be reused. Flush does not stop the -background workers or feature flag poller, and does not end open spans. - -Returning does not guarantee server receipt: pending work can remain after -a timeout or span export failure, and failed events may be dropped by the -consumers. Calls made directly from SDK callbacks such as `on_error` are -deferred rather than blocking the worker that invoked the callback. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`timeout_seconds` | `Optional[float]` | Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass `None` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently. The span exporter waits up to this many seconds for an in-flight flush, then uses a fresh drain budget once it acquires the lock. If the lock wait times out, the in-flight flush is left to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. Each span request is bounded by the client's `timeout` in seconds, and the last request is not cut short. This is therefore not a strict wall-clock limit for the whole call. With `None`, a retriable span failure is left for a later flush rather than retried within this call. | `10` - -**Examples:** - -```python -posthog.capture('event_name', distinct_id='user_id') -# Before a serverless invocation returns; keep the shared client open. -posthog.flush(timeout_seconds=10) -``` - -## Get feature flag - -### `Client.get_feature_flag` - -```python -get_feature_flag(key: str, distinct_id: ID_TYPES, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[FlagValue] -``` - -Get multivariate feature flag value for a user. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The feature flag key. | *required* -`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` -`only_evaluate_locally` | `bool` | Whether to only evaluate locally. | `False` -`send_feature_flag_events` | `bool` | Whether to send feature flag events. | `True` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` -`device_id` | `Optional[str]` | The device ID for this request. | `None` - -**Examples:** - -```python -enabled_variant = posthog.get_feature_flag('flag-key', 'distinct_id_of_your_user') -if enabled_variant == 'variant-key': # replace 'variant-key' with the key of your variant - # Do something differently for this user - # Optional: fetch the payload - matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user') -``` - -## Get feature flag payload - -### `Client.get_feature_flag_payload` - -```python -get_feature_flag_payload(key: str, distinct_id: ID_TYPES, *, match_value: Optional[FlagValue] = None, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = False, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[object] -``` - -Get the payload for a feature flag. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The feature flag key. | *required* -`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* -`match_value` | `Optional[FlagValue]` | The specific flag value to get payload for. | `None` -`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` -`only_evaluate_locally` | `bool` | Whether to only evaluate locally. | `False` -`send_feature_flag_events` | `bool` | Deprecated. Use get_feature_flag() instead if you need events. | `False` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` -`device_id` | `Optional[str]` | The device ID for this request. | `None` - -**Returns:** - -Type | Description ----- | ----------- -`Optional[object]` | The payload associated with the matched feature flag value, or None. -`Optional[object]` | This method returns the payload only, not the FeatureFlagResult wrapper -`Optional[object]` | used internally to compute it. - -**Examples:** - -```python -is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user') - -if is_my_flag_enabled: - # Do something differently for this user - # Optional: fetch the payload - matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user') -``` - -## Get feature flag result - -### `Client.get_feature_flag_result` - -```python -get_feature_flag_result(key: str, distinct_id: ID_TYPES, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[FeatureFlagResult] -``` - -Get a FeatureFlagResult object which contains the flag result and payload for a key by evaluating locally or remotely -depending on whether local evaluation is enabled and the flag can be locally evaluated. -This also captures the `$feature_flag_called` event unless `send_feature_flag_events` is `False`. - -**Examples:** - -```python -flag_result = posthog.get_feature_flag_result('flag-key', 'distinct_id_of_your_user') -if flag_result and flag_result.get_value() == 'variant-key': - # Do something differently for this user - # Optional: fetch the payload - matched_flag_payload = flag_result.payload -``` - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The feature flag key. | *required* -`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` -`only_evaluate_locally` | `bool` | Whether to only evaluate locally. | `False` -`send_feature_flag_events` | `bool` | Whether to send feature flag events. | `True` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` -`device_id` | `Optional[str]` | The device ID for this request. | `None` - -**Returns:** - -Type | Description ----- | ----------- -`Optional[FeatureFlagResult]` | Optional[FeatureFlagResult]: The feature flag result or None if disabled/not found. - -## Get feature flags - -### `Client.get_feature_variants` - -```python -get_feature_variants(distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, disable_geoip: Optional[bool] = None, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None) -> dict[str, Union[bool, str]] -``` - -Get feature flag variants for a user. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` -`flag_keys_to_evaluate` | `Optional[list[str]]` | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | `None` -`device_id` | `Optional[str]` | The device ID for this request. | `None` - -### `Client.get_all_flags` - -```python -get_all_flags(distinct_id: ID_TYPES, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None) -> Optional[dict[str, Union[bool, str]]] -``` - -Get all feature flags for a user. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` -`only_evaluate_locally` | `bool` | Whether to only evaluate locally. | `False` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` -`flag_keys_to_evaluate` | `Optional[list[str]]` | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | `None` -`device_id` | `Optional[str]` | The device ID for this request. | `None` - -**Examples:** - -```python -posthog.get_all_flags('distinct_id_of_your_user') -``` - -## Get feature flags and payloads - -### `Client.get_feature_flags_and_payloads` - -```python -get_feature_flags_and_payloads(distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, disable_geoip: Optional[bool] = None, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None) -> FlagsAndPayloads -``` - -Get feature flags and payloads for a user. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` -`flag_keys_to_evaluate` | `Optional[list[str]]` | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | `None` -`device_id` | `Optional[str]` | The device ID for this request. | `None` - -**Examples:** - -```python -result = posthog.get_feature_flags_and_payloads('') -``` - -### `Client.get_all_flags_and_payloads` - -```python -get_all_flags_and_payloads(distinct_id: ID_TYPES, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None) -> FlagsAndPayloads -``` - -Get all feature flags and their payloads for a user. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` -`only_evaluate_locally` | `bool` | Whether to only evaluate locally. | `False` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` -`flag_keys_to_evaluate` | `Optional[list[str]]` | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | `None` -`device_id` | `Optional[str]` | The device ID for this request. | `None` - -**Examples:** - -```python -posthog.get_all_flags_and_payloads('distinct_id_of_your_user') -``` - -## Evaluate flags - -### `Client.evaluate_flags` - -```python -evaluate_flags(distinct_id: Optional[ID_TYPES] = None, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, flag_keys: Optional[List[str]] = None, device_id: Optional[str] = None) -> FeatureFlagEvaluations -``` - -Evaluate all feature flags for a user in a single call and return a -`FeatureFlagEvaluations` snapshot. Branch on `.is_enabled()` / -`.get_flag()` and pass the same snapshot to `capture` via the -`flags` option so events carry the exact flag values the code branched on. - -Prefer this over repeated `get_feature_flag()` calls and over -`capture(send_feature_flags=True)` — it consolidates flag evaluation into -a single `/flags` request per incoming request. - -Local evaluation is transparent: when the poller resolves a flag, the -snapshot's `$feature_flag_called` events are tagged `locally_evaluated=True` -and reason `"Evaluated locally"`. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | `Optional[ID_TYPES]` | The user's distinct ID. If `None`, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot. | `None` -`groups` | `Optional[Mapping[str, Union[str, int]]]` | Mapping of group type to group key. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | Person properties to use for evaluation. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties keyed by group type. | `None` -`only_evaluate_locally` | `bool` | If True, never fall back to remote evaluation — flags that can't be evaluated locally are omitted from the snapshot and listed with a reason in its `unresolved_flags`. | `False` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup. | `None` -`flag_keys` | `Optional[List[str]]` | Optional list that scopes local evaluation, the underlying `/flags` request, and the returned snapshot. When omitted or `None`, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per `evaluate_flags` call unless `only_evaluate_locally` is True. If the server also does not know the key, it is omitted from the snapshot. | `None` -`device_id` | `Optional[str]` | Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes. | `None` - -**Returns:** - -Type | Description ----- | ----------- -`FeatureFlagEvaluations` | A `FeatureFlagEvaluations` snapshot. - -**Examples:** - -```python -flags = posthog.evaluate_flags( - "user_123", - person_properties={"plan": "enterprise"}, -) -if flags.is_enabled("new-dashboard"): - render_new_dashboard() -posthog.capture("page_viewed", distinct_id="user_123", flags=flags) -``` - -## Group identify - -### `Client.group_identify` - -```python -group_identify(group_type: str, group_key: str, properties: Optional[Dict[str, Any]] = None, timestamp: Optional[Union[datetime, str]] = None, uuid: Optional[Union[str, UUID]] = None, disable_geoip: Optional[bool] = None, distinct_id: Optional[ID_TYPES] = None) -> Optional[str] -``` - -Identify a group and set its properties. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`group_type` | `str` | The type of group (e.g., 'company', 'team'). Required - the call is dropped with a warning if it is missing or empty. | *required* -`group_key` | `str` | The unique identifier for the group. Required - the call is dropped with a warning if it is missing or empty. | *required* -`properties` | `Optional[Dict[str, Any]]` | A dictionary of properties to set on the group. | `None` -`timestamp` | `Optional[Union[datetime, str]]` | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | `None` -`uuid` | `Optional[Union[str, UUID]]` | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | `None` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this event. | `None` -`distinct_id` | `Optional[ID_TYPES]` | The distinct ID of the user performing the action. | `None` - -**Examples:** - -```python -posthog.group_identify('company', 'company_id_in_your_db', { - 'name': 'Awesome Inc.', - 'employees': 11 -}) -``` - -Note: This method will not raise exceptions. Errors are logged. - -## Identify - -### `Client.set` - -```python -set(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str] -``` - -Set properties on a person profile. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | | The distinct ID of the user. | -`properties` | | A dictionary of properties to set. | -`timestamp` | | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | -`uuid` | | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | -`disable_geoip` | | Whether to disable GeoIP for this event. | - -**Examples:** - -```python -# Set with distinct id -posthog.set(distinct_id='user123', properties={'name': 'Max Hedgehog'}) -``` - -Note: This method will not raise exceptions. Errors are logged. - -### `Client.set_once` - -```python -set_once(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str] -``` - -Set properties on a person profile only if they haven't been set before. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | | The distinct ID of the user. | -`properties` | | A dictionary of properties to set once. | -`timestamp` | | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | -`uuid` | | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | -`disable_geoip` | | Whether to disable GeoIP for this event. | - -**Examples:** - -```python -posthog.set_once(distinct_id='user123', properties={'initial_signup_date': '2024-01-01'}) -``` - -Note: This method will not raise exceptions. Errors are logged. - -## Is feature enabled - -### `Client.feature_enabled` - -```python -feature_enabled(key: str, distinct_id: ID_TYPES, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[bool] -``` - -Check if a feature flag is enabled for a user. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The feature flag key. | *required* -`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` -`only_evaluate_locally` | `bool` | Whether to only evaluate locally. | `False` -`send_feature_flag_events` | `bool` | Whether to send feature flag events. | `True` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` -`device_id` | `Optional[str]` | The device ID for this request. | `None` - -**Examples:** - -```python -is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user') -if is_my_flag_enabled: - # Do something differently for this user - # Optional: fetch the payload - matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user') -``` - -## Setup - -### `Client(...)` - -```python -__init__(project_api_key: str, host: str = None, debug: str = False, max_queue_size: str = 10000, send: str = True, on_error: str = None, flush_at: str = 100, flush_interval: str = 5.0, gzip: str = False, max_retries: str = 3, sync_mode: str = False, timeout: float = 15, thread: float = 1, poll_interval: float = 30, personal_api_key: float = None, disabled: float = False, disable_geoip: float = True, is_server: float = True, historical_migration: float = False, feature_flags_request_timeout_seconds: float = 3, feature_flags_request_max_retries: float = 1, super_properties: float = None, enable_exception_autocapture: float = False, log_captured_exceptions: float = False, project_root: float = None, privacy_mode: float = False, before_send: float = None, flag_fallback_cache_url: float = None, enable_local_evaluation: float = True, flag_definition_cache_provider: Optional[FlagDefinitionCacheProvider] = None, capture_exception_code_variables: Optional[FlagDefinitionCacheProvider] = False, code_variables_mask_patterns: Optional[FlagDefinitionCacheProvider] = None, code_variables_ignore_patterns: Optional[FlagDefinitionCacheProvider] = None, code_variables_mask_url_credentials: Optional[FlagDefinitionCacheProvider] = None, code_variables_detect_secrets: Optional[FlagDefinitionCacheProvider] = None, in_app_modules: list[str] | None = None, enable_exception_autocapture_rate_limiting: list[str] | None = False, exception_autocapture_bucket_size: list[str] | None = ExceptionCapture.DEFAULT_BUCKET_SIZE, exception_autocapture_refill_rate: list[str] | None = ExceptionCapture.DEFAULT_REFILL_RATE, exception_autocapture_refill_interval_seconds: list[str] | None = ExceptionCapture.DEFAULT_REFILL_INTERVAL_SECONDS, capture_mode: Optional[Union[CaptureMode, str]] = None, capture_compression: Optional[Union[CaptureCompression, str]] = None, secret_key: Optional[Union[CaptureCompression, str]] = None, metrics: Optional[dict] = None, enable_full_ai_capture: Optional[dict] = False, capture_trace_context: Optional[dict] = False, _use_ai_lane: Optional[dict] = False, _enable_multimodal_capture: Optional[dict] = False, traces: Optional[dict] = None, app_host: Optional[str] = None, email_request_timeout_seconds: float = 15, email_request_max_retries: int = 2) -``` - -Initialize a new PostHog client instance. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`project_api_key` | `str` | PostHog project API key/token. | *required* -`host` | | PostHog host. Defaults to the US ingestion endpoint when not set. App hosts such as `https://us.posthog.com` are mapped to the corresponding ingestion host. | `None` -`debug` | | Enable verbose SDK logging and re-raise errors from public API methods. | `False` -`max_queue_size` | | Maximum number of events buffered before upload. | `10000` -`send` | | If False, queueing succeeds but events are not sent. | `True` -`on_error` | | Optional callback invoked by background consumers when an upload fails. Keep it short and non-blocking. Calling lifecycle methods directly is safe and deferred, but do not start another thread or task that calls `flush()`, `join()`, or `shutdown()` and then wait for it from the callback. | `None` -`flush_at` | | Number of queued events that triggers a batch upload. | `100` -`flush_interval` | | Maximum seconds a background consumer waits before flushing a partial batch. | `5.0` -`gzip` | | Whether to gzip event upload payloads. | `False` -`max_retries` | | Number of upload retries. Values below 0 are treated as 0. | `3` -`sync_mode` | | If True, send each event synchronously instead of using background worker threads. This blocks the calling thread; in asyncio applications such as FastAPI, use `AsyncPosthog` instead. | `False` -`timeout` | `float` | HTTP request timeout in seconds for event uploads. | `15` -`thread` | | Number of background consumer threads. | `1` -`poll_interval` | | Seconds between local feature flag definition refreshes. | `30` -`secret_key` | | A Personal API Key or Project Secret API Key, used to authenticate local feature flag evaluation, remote config payloads, and decrypted flag payloads. Example::
posthog.Client(project_api_key, secret_key="phx_...") | `None` -`personal_api_key` | | Deprecated alias for `secret_key`. Still honored for backwards compatibility; prefer `secret_key`, which also accepts a Project Secret API Key. | `None` -`disabled` | | If True, disable captures and API requests. Useful in tests. | `False` -`disable_geoip` | | Whether to disable server-side GeoIP enrichment. Defaults to True. | `True` -`is_server` | | Whether events are emitted from a server-side runtime. Defaults to True; set to False when using the SDK as a client/CLI so the device OS is attributed to the person normally. | `True` -`historical_migration` | | Mark events as historical migration imports. | `False` -`feature_flags_request_timeout_seconds` | `float` | Timeout in seconds for feature flag and remote config requests. | `3` -`feature_flags_request_max_retries` | | Number of retries for feature flag requests after network, transport, or timeout failures. Defaults to 1. Set to 0 to disable retries. | `1` -`app_host` | `Optional[str]` | Your PostHog app host, such as `https://posthog.example.com`. `emails` sends there. Needed only when `host` is not PostHog Cloud, for example a reverse proxy, which must not receive the secret key. | `None` -`email_request_timeout_seconds` | `float` | Timeout in seconds for each email send request. Defaults to 15. | `15` -`email_request_max_retries` | `int` | Number of retries for an email send after a network error, a rate limit, or an unavailable service. Defaults to 2. Set to 0 when a job runner already retries the send. | `2` -`super_properties` | | Properties merged into every captured event. | `None` -`enable_exception_autocapture` | | Automatically capture uncaught exceptions. | `False` -`log_captured_exceptions` | | Also log exceptions captured by error tracking. | `False` -`project_root` | | Root path used to determine in-app stack frames for captured exceptions. Defaults to the current working directory. | `None` -`privacy_mode` | | For AI observability, capture usage metadata without prompt inputs or outputs. | `False` -`enable_full_ai_capture` | | Route PostHog AI wrapper events through the dedicated AI capture endpoint and capture full AI content: skips string truncation and passes media (base64/data URIs) through unredacted. `privacy_mode` always wins. Defaults to False. | `False` -`before_send` | | Optional callback that can modify or drop events before upload. Return `None` to drop an event. | `None` -`flag_fallback_cache_url` | | Optional feature flag fallback cache URL, such as `memory://local/?ttl=300&size=10000` or a Redis URL. | `None` -`enable_local_evaluation` | | Whether to poll feature flag definitions for local evaluation when a personal API key is configured. | `True` -`flag_definition_cache_provider` | `Optional[FlagDefinitionCacheProvider]` | Optional external cache provider for sharing feature flag definitions across workers. | `None` -`capture_exception_code_variables` | | Capture local variable values on exception stack frames. | `False` -`capture_trace_context` | | When OpenTelemetry is installed and a valid span is active at capture time, add its trace and span IDs as `$trace_id` and `$span_id` properties to events captured with `capture()` and `capture_ai()`, so they can be correlated with backend traces. Explicit `$trace_id`/`$span_id` values passed in `properties` win. Exception events (`capture_exception`) always attach these IDs regardless of this setting. Defaults to False. | `False` -`traces` | `Optional[dict]` | Config dict for distributed tracing: `service_name`, `service_version`, `environment`, `resource_attributes`, `flush_interval` (5 s), `max_queue_size` (2048), `max_export_batch_size` (512), `max_live_spans` (10000), `max_span_age` (3600 s), `max_attributes_per_span` (128), `max_events_per_span` (128), `max_attribute_value_length` (8192). `before_span_send` is a callable, or a list run in order, that receives each finished span as a dict (`trace_id`, `span_id` and `parent_span_id` are read-only) and returns it, edited, or `None` to drop it; a hook that raises drops the span. Tracing is off until this is provided. Spans export on a background timer even with `sync_mode`; serverless handlers should call `flush()` before returning. Defaults to None. | `None` -`code_variables_mask_patterns` | | Variable-name patterns to mask when capturing code variables. | `None` -`code_variables_ignore_patterns` | | Variable-name patterns to omit when capturing code variables. | `None` -`code_variables_mask_url_credentials` | | Scrub credentials embedded in URLs/DSNs (e.g. `user:pass@host`) from captured code variables, regardless of the surrounding variable name. Defaults to True. | `None` -`code_variables_detect_secrets` | | Last-resort entropy-based detection that redacts high-entropy secret-looking values (API keys, tokens, strong passwords) sitting in innocuously-named variables, after the name and URL checks. Skips structured ids (UUIDs, ObjectIds, hashes). Defaults to True. | `None` -`in_app_modules` | `list[str] \| None` | Module/package prefixes treated as in-app frames in captured exceptions. | `None` -`enable_exception_autocapture_rate_limiting` | | Rate limit autocaptured exceptions client-side with a token bucket per exception type. Disabled by default. | `False` -`exception_autocapture_bucket_size` | | Maximum burst of autocaptured exceptions allowed per exception type (token bucket size, clamped to 0-100). | `DEFAULT_BUCKET_SIZE` -`exception_autocapture_refill_rate` | | Tokens restored per refill interval for each exception type's bucket. | `DEFAULT_REFILL_RATE` -`exception_autocapture_refill_interval_seconds` | | Seconds between token refills for autocaptured exception rate limiting. | `DEFAULT_REFILL_INTERVAL_SECONDS` -`capture_mode` | `Optional[Union[CaptureMode, str]]` | Capture wire protocol to use. Defaults to `CaptureMode.V0` (legacy `/batch/`). Set `CaptureMode.V1` (or pass the string `"v1"`) to opt into `/i/v1/analytics/events`. When omitted, the `POSTHOG_CAPTURE_MODE` env var is consulted, then `V0`. | `None` -`capture_compression` | `Optional[Union[CaptureCompression, str]]` | Request-body compression for capture-v1 uploads (ignored in V0, which uses `gzip`). `CaptureCompression.GZIP` or `DEFLATE` (or the strings `"gzip"`/`"deflate"`). When omitted, the `POSTHOG_CAPTURE_COMPRESSION` env var is consulted, then the legacy `gzip` flag, then no compression. | `None` - -**Examples:** - -```python -from posthog import Posthog - -posthog = Posthog('', host='') -``` - -## Shutdown - -### `Client.join` - -```python -join() -> None -``` - -Attempt to process queued events and end the consumer threads. Do not use directly, call `shutdown()` instead. - -Failed or undrainable events may be dropped and reported through logging -or `on_error`; returning does not guarantee server receipt. Lifecycle -cleanup is attempted once, and cleanup failures are logged without retry. - -**Examples:** - -```python -posthog.join() -``` - -### `Client.shutdown` - -```python -shutdown() -> None -``` - -Attempt final delivery and permanently shut down the client. - -Call this for final process cleanup or when disposing of a client that -will not be reused. It closes capture lanes, stops background workers and -the feature flag poller, and tears down integrations. Do not call it at the -end of each request or serverless invocation when the client is shared; -call `flush()` before returning instead. A shut-down client cannot be -reused; create a new client if needed. - -Normally this method blocks until queued events have been attempted and -cleanup finishes. Failed or undrainable events may be dropped and -reported through logging or `on_error`; returning does not guarantee -server receipt. Event draining has no timeout. Queued spans get one final -flush with up to 30 seconds waiting for an in-flight flush, then a fresh -30-second drain budget; the last HTTP request is not cut short. Any spans -it cannot send are discarded with a warning, as are spans still open. -Lifecycle cleanup is attempted once, and cleanup failures -are logged without retry. When called directly from an SDK callback such as -`on_error`, shutdown is deferred to avoid blocking the worker that -invoked the callback. If the callback must coordinate a blocking -shutdown, have it signal an -application-owned thread and return before that thread calls shutdown. -Do not wait inside the callback for another thread or task that calls a -lifecycle method. - -**Examples:** - -```python -posthog.shutdown() -``` - -## Flag definition loader - -### `Client.load_feature_flags` - -```python -load_feature_flags() -``` - -Load feature flags for local evaluation. - -**Examples:** - -```python -posthog.load_feature_flags() -``` - -### `Client.feature_flag_definitions` - -```python -feature_flag_definitions() -``` - -Return feature flag definitions loaded for local evaluation. - -**Returns:** - -Type | Description ----- | ----------- - | The currently loaded feature flag definitions, or `None` before - | local evaluation has loaded definitions. - -## SDK utils - -### `Client.queue` - -```python -queue: Queue -``` - -The analytics lane's queue (kept for backwards compatibility). - -### `Client.consumers` - -```python -consumers: Optional[List[Consumer]] -``` - -Flat list of the lanes' consumers, analytics first (kept for backwards compatibility). - -### `Client.new_context` - -```python -new_context(fresh = False, capture_exceptions: Optional[bool] = None) -``` - -Create a new context for managing shared state. Learn more about [contexts](/docs/libraries/python#contexts). - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`fresh` | | Whether to create a fresh context that doesn't inherit from parent. | `False` -`capture_exceptions` | `Optional[bool]` | Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting. | `None` - -**Examples:** - -```python -with client.new_context(): - client.identify_context('') - client.capture('event_name') -``` - -
-Category - -Contexts - -
- -### `Client.scoped` - -```python -scoped(fresh = False, capture_exceptions: Optional[bool] = None) -``` - -Decorator that creates a new context for the wrapped function using this client. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`fresh` | | Whether to create a fresh context that doesn't inherit from parent. | `False` -`capture_exceptions` | `Optional[bool]` | Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting. | `None` - -
-Category - -Contexts - -
- -### `Client.tag` - -```python -tag(name: str, value: Any) -> None -``` - -Add a tag to the current context. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`name` | `str` | The tag key. | *required* -`value` | `Any` | The tag value. | *required* - -
-Category - -Contexts - -
- -### `Client.get_tags` - -```python -get_tags() -> Dict[str, Any] -``` - -Get all tags from the current context. - -**Returns:** - -Type | Description ----- | ----------- -`Dict[str, Any]` | Dict of all tags in the current context. - -
-Category - -Contexts - -
- -### `Client.identify_context` - -```python -identify_context(distinct_id: str) -> None -``` - -Identify the current context with a distinct ID. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | `str` | The distinct ID to associate with the current context and its children. | *required* - -
-Category - -Contexts - -
- -### `Client.set_context_session` - -```python -set_context_session(session_id: str) -> None -``` - -Set the session ID for the current context. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`session_id` | `str` | The session ID to associate with the current context and its children. | *required* - -
-Category - -Contexts - -
- -### `Client.set_context_device_id` - -```python -set_context_device_id(device_id: str) -> None -``` - -Set the device ID for the current context. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`device_id` | `str` | The device ID to associate with the current context and its children. | *required* - -
-Category - -Contexts - -
- -### `Client.feature_flags` - -```python -feature_flags -``` - -Get the local evaluation feature flags. - -### `Client.get_feature_payloads` - -```python -get_feature_payloads(distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, disable_geoip: Optional[bool] = None, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None) -> dict[str, Optional[str]] -``` - -Get feature flag payloads for a user, preserving valid serialized JSON. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` -`flag_keys_to_evaluate` | `Optional[list[str]]` | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | `None` -`device_id` | `Optional[str]` | The device ID for this request. | `None` - -**Examples:** - -```python -payloads = posthog.get_feature_payloads('') -``` - -
-Category - -Feature flags - -
- -### `Client.get_flags_decision` - -```python -get_flags_decision(distinct_id: Optional[ID_TYPES] = None, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, disable_geoip: Optional[bool] = None, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None) -> FlagsResponse -``` - -Get feature flags decision. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | `Optional[ID_TYPES]` | The distinct ID of the user. | `None` -`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` -`flag_keys_to_evaluate` | `Optional[list[str]]` | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | `None` -`device_id` | `Optional[str]` | The device ID for this request. | `None` - -**Examples:** - -```python -decision = posthog.get_flags_decision('user123') -``` - -
-Category - -Feature flags - -
- -### `Client.metrics` - -```python -metrics: PostHogMetrics -``` - -The `posthog.metrics` API: a statsd-style pre-aggregating metrics client — alpha. - -Samples fold into per-series aggregates in memory and flush as one OTLP -data point per series per window, so recording from hot paths is cheap. -Configure via the `metrics` client option; pending metrics flush on -`shutdown()`. - -**Examples:** - -```python -client = Posthog("", metrics={"service_name": "billing-worker"}) -client.metrics.count("invoices.processed", 1, attributes={"plan": "pro"}) -client.metrics.gauge("queue.depth", 42) -client.metrics.histogram("job.duration", 187, unit="ms") -``` - -### `Client.get_remote_config_payload` - -```python -get_remote_config_payload(key: str) -``` - -Get the payload for a remote config feature flag. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The remote config feature flag key. | *required* - -**Returns:** - -Type | Description ----- | ----------- - | The payload associated with the feature flag, or `None` if the - | client is disabled, no personal API key is configured, or the request - | fails. Encrypted payloads are decrypted by PostHog before being - | returned. - -
-Note - -Requires `secret_key` for authentication. - -
- -
-Category - -Feature flags - -
- -### `Client.get_feature_flag_evaluation_runtime` - -```python -get_feature_flag_evaluation_runtime(key: str) -> Optional[FeatureFlagEvaluationRuntime] -``` - -Return where a locally loaded feature flag is meant to be evaluated. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The feature flag key. | *required* - -**Returns:** - -Type | Description ----- | ----------- -`Optional[FeatureFlagEvaluationRuntime]` | The flag's evaluation runtime, or `None` when local evaluation has -`Optional[FeatureFlagEvaluationRuntime]` | not loaded a definition for this key. A definition that carries no -`Optional[FeatureFlagEvaluationRuntime]` | runtime reports `FeatureFlagEvaluationRuntime.ALL`, the default -`Optional[FeatureFlagEvaluationRuntime]` | PostHog applies. - -**Examples:** - -```python -from posthog import FeatureFlagEvaluationRuntime - -runtime = posthog.get_feature_flag_evaluation_runtime("my-flag") -if runtime is FeatureFlagEvaluationRuntime.SERVER: - ... -``` - -
-Category - -Feature flags - -
- -### `Client.get_feature_flag_keys_by_evaluation_runtime` - -```python -get_feature_flag_keys_by_evaluation_runtime(evaluation_runtime: Union[FeatureFlagEvaluationRuntime, str]) -> list[str] -``` - -Return the keys of locally loaded flags that a runtime can evaluate. - -A flag set to `FeatureFlagEvaluationRuntime.ALL` suits either runtime, -so it is returned for `CLIENT` and for `SERVER`, and asking for -`ALL` returns every loaded flag. Use this to decide which flags to hand -to a browser when a backend serves flags to its own frontend. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`evaluation_runtime` | `Union[FeatureFlagEvaluationRuntime, str]` | The runtime to match, as a `FeatureFlagEvaluationRuntime` or its string value. | *required* - -**Returns:** - -Type | Description ----- | ----------- -`list[str]` | The matching flag keys, in the order local evaluation loaded them. -`list[str]` | Empty when no definitions are loaded. - -**Examples:** - -```python -from posthog import FeatureFlagEvaluationRuntime - -client_keys = posthog.get_feature_flag_keys_by_evaluation_runtime( - FeatureFlagEvaluationRuntime.CLIENT -) -``` - -
-Category - -Feature flags - -
- -### `posthog.integrations.django.PosthogContextMiddleware` - -```python -PosthogContextMiddleware(get_response) -``` - -Middleware to automatically track Django requests. - -This middleware wraps all calls with a posthog context. It attempts to extract the following from the request: -- Session ID, (extracted from `X-POSTHOG-SESSION-ID`) -- Distinct ID, (extracted from `X-POSTHOG-DISTINCT-ID`, falling back to the authenticated request user ID) -- Authenticated user email as `email` -- Request URL as `$current_url` -- Request method as `$request_method` -- Request path as `$request_path` -- Forwarded IP address as `$ip` -- User agent as `$user_agent` - -Configure the integration with these Django settings: -- `POSTHOG_MW_CLIENT`: client used for automatic exception capture, or the global client. - This does not redirect events explicitly captured through other clients. -- `POSTHOG_MW_CAPTURE_EXCEPTIONS`: `False` enables context enrichment only; - `True` enables automatic exception capture; `None` inherits the effective client's - `enable_exception_autocapture`. When omitted, the legacy default is `True`. -- `POSTHOG_MW_REQUEST_FILTER`: a callback receiving the request. Returning `False` - disables both enrichment and automatic exception capture for that request. -- `POSTHOG_MW_EXTRA_PROPERTIES`: a callback returning additional event properties. -- `POSTHOG_MW_CAPTURE_TRACING_HEADERS`: read PostHog identity and session headers - as request-scoped analytics context (default: `True`). Explicit capture IDs and - event properties take precedence. These values are never authentication or - authorization. Authenticated Django user identity remains the fallback when no - usable header ID is available or header capture is disabled. -- `POSTHOG_MW_TRUST_TRACING_HEADERS`: deprecated alias for - `POSTHOG_MW_CAPTURE_TRACING_HEADERS`; the capture-named setting takes precedence. -- `POSTHOG_MW_PROPERTIES_MAP`: an optional callback transforming request properties. - -The old `POSTHOG_MW_EXTRA_TAGS` and `POSTHOG_MW_TAG_MAP` settings remain aliases; -the property-named settings take precedence when both are configured. - -Request properties are included on all events captured within the active context, -including when automatic exception capture is disabled. -See the context documentation for more information. The extracted distinct ID and session ID, -if found, are used to associate all events captured in the middleware context with the same distinct ID -and session as currently active on the frontend. See the documentation for `set_context_session` -and `identify_context` for more details. - -This middleware is hybrid-capable: it supports both WSGI (sync) and ASGI (async) Django applications. The middleware -detects at initialization whether the next middleware in the chain is async or sync, and adapts its behavior accordingly. -This ensures compatibility with both pure sync and pure async middleware chains, as well as mixed chains in ASGI mode. - -**Functions:** - -Name | Description ----- | ----------- -`aextract_properties` | Async version of extract_properties for use in async request handling. -`aextract_request_user` | Async version of extract_request_user for use in async request handling. -`aextract_tags` | Compatibility alias for `aextract_properties`. -`extract_properties` | Extract event properties and identity from a synchronous request. -`extract_request_user` | Extract user ID and email from request in sync context. -`extract_tags` | Compatibility alias for `extract_properties`. -`process_exception` | Process exceptions from views and downstream middleware. - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`async_capable` | | -`capture_exceptions` | `Optional[bool]` | -`capture_tracing_headers` | | -`client` | | -`extra_properties` | | -`extra_tags` | | Compatibility alias for `extra_properties`. -`get_response` | | -`properties_map` | | -`request_filter` | | -`sync_capable` | | -`tag_map` | | Compatibility alias for `properties_map`. -`trust_tracing_headers` | `bool` | Deprecated alias for `capture_tracing_headers`. - -Initialize the middleware with Django's next handler. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`get_response` | | The next middleware or view handler in Django's middleware chain. May be synchronous or asynchronous. | - -#### `posthog.integrations.django.PosthogContextMiddleware.aextract_properties` - -```python -aextract_properties(request) -``` - -Async version of extract_properties for use in async request handling. - -Uses await request.auser() instead of request.user to avoid -SynchronousOnlyOperation in async context. - -Follows Django's naming convention for async methods (auser, asave, etc.). - -#### `posthog.integrations.django.PosthogContextMiddleware.aextract_request_user` - -```python -aextract_request_user(request) -``` - -Async version of extract_request_user for use in async request handling. - -Uses await request.auser() instead of request.user to avoid -SynchronousOnlyOperation in async context. - -Follows Django's naming convention for async methods (auser, asave, etc.). - -#### `posthog.integrations.django.PosthogContextMiddleware.aextract_tags` - -```python -aextract_tags(request) -``` - -Compatibility alias for `aextract_properties`. - -#### `posthog.integrations.django.PosthogContextMiddleware.async_capable` - -```python -async_capable = True -``` - -#### `posthog.integrations.django.PosthogContextMiddleware.capture_exceptions` - -```python -capture_exceptions: Optional[bool] = capture_exceptions if (capture_exceptions is None or isinstance(capture_exceptions, bool)) else True -``` - -#### `posthog.integrations.django.PosthogContextMiddleware.capture_tracing_headers` - -```python -capture_tracing_headers = _resolve_capture_tracing_headers(capture_tracing_headers, trust_tracing_headers, canonical_name='POSTHOG_MW_CAPTURE_TRACING_HEADERS', deprecated_name='POSTHOG_MW_TRUST_TRACING_HEADERS') -``` - -#### `posthog.integrations.django.PosthogContextMiddleware.client` - -```python -client = cast('Optional[Client]', settings.POSTHOG_MW_CLIENT) -``` - -#### `posthog.integrations.django.PosthogContextMiddleware.extra_properties` - -```python -extra_properties = cast('Callable[[HttpRequest], Dict[str, Any]]', extra_properties) if callable(extra_properties) else None -``` - -#### `posthog.integrations.django.PosthogContextMiddleware.extra_tags` - -```python -extra_tags -``` - -Compatibility alias for `extra_properties`. - -#### `posthog.integrations.django.PosthogContextMiddleware.extract_properties` - -```python -extract_properties(request) -``` - -Extract event properties and identity from a synchronous request. - -#### `posthog.integrations.django.PosthogContextMiddleware.extract_request_user` - -```python -extract_request_user(request) -``` - -Extract user ID and email from request in sync context. - -#### `posthog.integrations.django.PosthogContextMiddleware.extract_tags` - -```python -extract_tags(request) -``` - -Compatibility alias for `extract_properties`. - -#### `posthog.integrations.django.PosthogContextMiddleware.get_response` - -```python -get_response = get_response -``` - -#### `posthog.integrations.django.PosthogContextMiddleware.process_exception` - -```python -process_exception(request, exception) -``` - -Process exceptions from views and downstream middleware. - -Django calls this WHILE still inside the context created by __call__, -so request tags have already been extracted and set. This method just -needs to capture the exception directly. - -Django converts view exceptions into responses before they propagate through -the middleware stack, so the context manager in __call__/__acall__ never sees them. - -Note: Django's process_exception is always synchronous, even for async views. - -#### `posthog.integrations.django.PosthogContextMiddleware.properties_map` - -```python -properties_map = cast('Callable[[Dict[str, Any]], Dict[str, Any]]', properties_map) if callable(properties_map) else None -``` - -#### `posthog.integrations.django.PosthogContextMiddleware.request_filter` - -```python -request_filter = cast('Optional[Callable[[HttpRequest], bool]]', settings.POSTHOG_MW_REQUEST_FILTER) -``` - -#### `posthog.integrations.django.PosthogContextMiddleware.sync_capable` - -```python -sync_capable = True -``` - -#### `posthog.integrations.django.PosthogContextMiddleware.tag_map` - -```python -tag_map -``` - -Compatibility alias for `properties_map`. - -#### `posthog.integrations.django.PosthogContextMiddleware.trust_tracing_headers` - -```python -trust_tracing_headers: bool -``` - -Deprecated alias for `capture_tracing_headers`. - -### `posthog.integrations.flask.PosthogFlaskIntegration` - -```python -PosthogFlaskIntegration(app: Optional[Flask] = None, *, client: Optional[Client] = None, capture_exceptions: Optional[bool] = None, request_filter: Optional[Callable[[Request], bool]] = None, extra_properties: Optional[Callable[[Request], Mapping[str, Any]]] = None, trust_tracing_headers: Optional[bool] = None, capture_tracing_headers: Optional[bool] = None) -> None -``` - -Add PostHog request context and exception tracking to a Flask app. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`app` | `Optional[Flask]` | An optional Flask application. If omitted, call `init_app` later (the Flask application-factory pattern). | `None` -`client` | `Optional[Client]` | Optional destination for automatically captured exceptions. The global client is used by default. Request context still enriches events captured through any client and does not redirect capture calls. | `None` -`capture_exceptions` | `Optional[bool]` | Capture exceptions that reach Flask's unhandled exception machinery. If omitted, inherit the effective client's `enable_exception_autocapture` setting. Pass `True` or `False` to override it. | `None` -`request_filter` | `Optional[Callable[[Request], bool]]` | Optional callback receiving Flask's request object. A false return value disables both context and exception capture for that request. | `None` -`extra_properties` | `Optional[Callable[[Request], Mapping[str, Any]]]` | Optional callback returning additional event properties. This is useful for application-specific metadata such as an authenticated user's role. Values should not contain secrets or request bodies. | `None` -`capture_tracing_headers` | `Optional[bool]` | Read PostHog distinct and session ID headers as request-scoped analytics context. Defaults to True. Explicit capture IDs and event properties take precedence. Never use these values for security. | `None` -`trust_tracing_headers` | `Optional[bool]` | Deprecated alias for `capture_tracing_headers`. The capture-named option takes precedence when both are supplied. | `None` - -The integration intentionally does not capture exceptions handled by an -application error handler, expected HTTP exceptions, request or response -bodies, query strings, cookies, authorization headers, or arbitrary headers. -Call `capture_exception` explicitly from a custom error handler if a -handled exception should be reported. - -**Functions:** - -Name | Description ----- | ----------- -`init_app` | Register the integration with a Flask application once. - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`capture_exceptions` | | -`capture_tracing_headers` | | -`client` | | -`extra_properties` | | -`request_filter` | | -`trust_tracing_headers` | `bool` | Deprecated alias for `capture_tracing_headers`. - -#### `posthog.integrations.flask.PosthogFlaskIntegration.capture_exceptions` - -```python -capture_exceptions = capture_exceptions -``` - -#### `posthog.integrations.flask.PosthogFlaskIntegration.capture_tracing_headers` - -```python -capture_tracing_headers = _resolve_capture_tracing_headers(capture_tracing_headers, trust_tracing_headers) -``` - -#### `posthog.integrations.flask.PosthogFlaskIntegration.client` - -```python -client = client -``` - -#### `posthog.integrations.flask.PosthogFlaskIntegration.extra_properties` - -```python -extra_properties = extra_properties -``` - -#### `posthog.integrations.flask.PosthogFlaskIntegration.init_app` - -```python -init_app(app: Flask) -> None -``` - -Register the integration with a Flask application once. - -#### `posthog.integrations.flask.PosthogFlaskIntegration.request_filter` - -```python -request_filter = request_filter -``` - -#### `posthog.integrations.flask.PosthogFlaskIntegration.trust_tracing_headers` - -```python -trust_tracing_headers: bool -``` - -Deprecated alias for `capture_tracing_headers`. - -### `posthog.integrations.asgi.PosthogASGIMiddleware` - -```python -PosthogASGIMiddleware(app: _ASGIApp, client: Optional[Client] = None, capture_exceptions: Optional[bool] = None, request_filter: Optional[_RequestFilter] = None, extra_properties: Optional[_ExtraProperties] = None, trust_tracing_headers: Optional[bool] = None, *, capture_tracing_headers: Optional[bool] = None) -> None -``` - -Add PostHog context and automatic exception capture to an ASGI app. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`app` | `_ASGIApp` | The downstream ASGI application. | *required* -`client` | `Optional[Client]` | Optional destination for automatically captured exceptions. The global client is used by default. Request context still enriches events captured through any client and does not redirect capture calls. | `None` -`capture_exceptions` | `Optional[bool]` | Capture exceptions escaping the downstream app. If omitted, inherit the effective client's `enable_exception_autocapture` setting. Pass `True` or `False` to override it. | `None` -`request_filter` | `Optional[_RequestFilter]` | Optional sync or async callback receiving the ASGI scope. Returning `False` bypasses all instrumentation for that scope. | `None` -`extra_properties` | `Optional[_ExtraProperties]` | Optional sync or async callback receiving the ASGI scope and returning additional event properties. | `None` -`capture_tracing_headers` | `Optional[bool]` | Read PostHog distinct and session ID headers as request-scoped analytics context. Defaults to True. Explicit capture IDs and event properties take precedence. Never use these values for security. | `None` -`trust_tracing_headers` | `Optional[bool]` | Deprecated alias for `capture_tracing_headers`. The capture-named option takes precedence when both are supplied. | `None` - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`app` | | -`capture_exceptions` | | -`capture_tracing_headers` | | -`client` | | -`extra_properties` | | -`request_filter` | | -`trust_tracing_headers` | `bool` | Deprecated alias for `capture_tracing_headers`. - -#### `posthog.integrations.asgi.PosthogASGIMiddleware.app` - -```python -app = app -``` - -#### `posthog.integrations.asgi.PosthogASGIMiddleware.capture_exceptions` - -```python -capture_exceptions = capture_exceptions -``` - -#### `posthog.integrations.asgi.PosthogASGIMiddleware.capture_tracing_headers` - -```python -capture_tracing_headers = _resolve_capture_tracing_headers(capture_tracing_headers, trust_tracing_headers) -``` - -#### `posthog.integrations.asgi.PosthogASGIMiddleware.client` - -```python -client = client -``` - -#### `posthog.integrations.asgi.PosthogASGIMiddleware.extra_properties` - -```python -extra_properties = extra_properties -``` - -#### `posthog.integrations.asgi.PosthogASGIMiddleware.request_filter` - -```python -request_filter = request_filter -``` - -#### `posthog.integrations.asgi.PosthogASGIMiddleware.trust_tracing_headers` - -```python -trust_tracing_headers: bool -``` - -Deprecated alias for `capture_tracing_headers`. - -### `posthog.integrations.celery.PosthogCeleryIntegration` - -```python -PosthogCeleryIntegration(client: Optional[Client] = None, capture_exceptions: bool = True, capture_task_lifecycle_events: bool = True, propagate_context: bool = True, task_filter: Optional[Callable[[Optional[str], dict[str, Any]], bool]] = None) -``` - -Celery integration that captures task lifecycle events and exceptions. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`client` | `Optional[Client]` | Optional `Client` instance. When provided, all events and exceptions are captured through this client rather than the global `posthog` module. Don't skip this if using a custom flag definition cache provider, and pass the custom `Client` instance here initialized with the custom provider so fork safety for that provider is handled correctly. | `None` -`capture_exceptions` | `bool` | Whether to capture task exceptions via `capture_exception` (default `True`). | `True` -`capture_task_lifecycle_events` | `bool` | Whether to emit lifecycle events of the task such as "started", "success", "failure" etc. (default `True`). | `True` -`propagate_context` | `bool` | Whether to propagate PostHog context (distinct ID, session ID, tags) from the producer to the worker via task headers (default `True`). | `True` -`task_filter` | `Optional[Callable[[Optional[str], dict[str, Any]], bool]]` | Optional callback `(task_name, task_properties) -> bool` expected to return `False` if a given task should not be tracked. | `None` - -**Functions:** - -Name | Description ----- | ----------- -`instrument` | Connect Celery signal handlers to capture task events and exceptions. -`shutdown` | Disconnect all signal handlers registered by `instrument()`, flush all pending events -`uninstrument` | Disconnect Celery signal handlers and unregister exit cleanup. - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`capture_exceptions` | | -`capture_task_lifecycle_events` | | -`client` | | -`propagate_context` | | -`task_filter` | | - -#### `posthog.integrations.celery.PosthogCeleryIntegration.capture_exceptions` - -```python -capture_exceptions = capture_exceptions -``` - -#### `posthog.integrations.celery.PosthogCeleryIntegration.capture_task_lifecycle_events` - -```python -capture_task_lifecycle_events = capture_task_lifecycle_events -``` - -#### `posthog.integrations.celery.PosthogCeleryIntegration.client` - -```python -client = client -``` - -#### `posthog.integrations.celery.PosthogCeleryIntegration.instrument` - -```python -instrument() -> None -``` - -Connect Celery signal handlers to capture task events and exceptions. -Call this after initializing the PostHog client and this integration. - -If Celery runs on a single host, reinstrumenting in worker children is -not strictly necessary because the PostHog client and this integration -are fork-safe. If Celery workers run across multiple hosts, each worker -process must initialize PostHog, this integration, and call -`instrument()`. Celery provides `worker_process_init` signal to help -with this. - -#### `posthog.integrations.celery.PosthogCeleryIntegration.propagate_context` - -```python -propagate_context = propagate_context -``` - -#### `posthog.integrations.celery.PosthogCeleryIntegration.shutdown` - -```python -shutdown() -> None -``` - -Disconnect all signal handlers registered by `instrument()`, flush all pending events -and cleanly shutdown the integration. - -`shutdown()` is also registered on `worker_process_shutdown` and `atexit` signals, -but there is no guarantee those will always be called, so we strongly recommend calling -it manually when the integration is no longer needed to avoid data loss. - -#### `posthog.integrations.celery.PosthogCeleryIntegration.task_filter` - -```python -task_filter = task_filter -``` - -#### `posthog.integrations.celery.PosthogCeleryIntegration.uninstrument` - -```python -uninstrument() -> None -``` - -Disconnect Celery signal handlers and unregister exit cleanup. - -Do not use directly, call `shutdown()` instead. - -### `posthog.request.set_socket_options` - -```python -set_socket_options(socket_options: Optional[SocketOptions]) -> None -``` - -Configure socket options for all SDK HTTP connections. - -Call this during initialization, before making API requests. Pass `None` -to reset to the default socket behavior. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`socket_options` | `Optional[SocketOptions]` | A list of `(level, option, value)` tuples accepted by urllib3/`socket.setsockopt()`, or `None` to reset defaults. | *required* - -
-Example - -from posthog import set_socket_options -set_socket_options([(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)]) - -
- -### `posthog.request.enable_keep_alive` - -```python -enable_keep_alive() -> None -``` - -Enable TCP keepalive for SDK HTTP connections. - -This helps prevent idle pooled connections from being dropped by network -infrastructure. Call during initialization, before making API requests. - -### `posthog.request.disable_connection_reuse` - -```python -disable_connection_reuse() -> None -``` - -Disable HTTP connection reuse for SDK requests. - -Each request will create a fresh connection. This can avoid issues with -environments that terminate pooled connections, but adds per-request -overhead. Call during initialization, before making API requests. - -## AsyncClient - -Needs the `async` extra, `posthog[async]`. - -### `AsyncClient.alias` - -```python -alias(previous_id: ID_TYPES, distinct_id: Optional[str], timestamp: Optional[Union[datetime, str]] = None, uuid: Optional[str] = None, disable_geoip: Optional[bool] = None) -> Optional[str] -``` - -### `AsyncClient.capture` - -```python -capture(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] -``` - -Queue an event without blocking for network delivery. - -### `AsyncClient.capture_immediate` - -```python -capture_immediate(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] -``` - -Capture one event and wait until its delivery attempt completes. - -### `AsyncClient.capture_exception` - -```python -capture_exception(exception: Optional[ExceptionArg] = None, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] -``` - -Capture an exception. This method never raises, including in debug mode. - -### `AsyncClient.emails` - -```python -emails: AsyncEmails -``` - -Send transactional email from PostHog email templates, and read the -status of each email sent with `get`. - -Needs `secret_key`, a project secret API key with the `hog_flow:write` -scope, and `posthog[async]`. Sends go to `app_host`, or to the PostHog -Cloud app host of `host`. `email_request_timeout_seconds` and -`email_request_max_retries` configure each send. A disabled or shut-down -client sends nothing. See `posthog.emails` for setup. - -**Examples:** - -```python -await client.emails.send( - to="user@example.com", - template_id="0190f6d2-7c1e-7b3a-9f5e-1c2d3e4f5a6b", - distinct_id="user_123", -) -``` - -### `AsyncClient.flush` - -```python -flush(timeout_seconds: Optional[float] = 10) -> None -``` - -### `AsyncClient.evaluate_flags` - -```python -evaluate_flags(distinct_id: Optional[ID_TYPES] = None, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, disable_geoip: Optional[bool] = None, flag_keys: Optional[list[str]] = None, device_id: Optional[str] = None) -> FeatureFlagEvaluations -``` - -Evaluate flags remotely without blocking the event loop. - -The returned snapshot has synchronous in-memory accessors. Pass it to -`capture(flags=...)` to attach the exact values used for branching. -This client does not evaluate flags locally, so the snapshot's -`unresolved_flags` is always empty. - -### `AsyncClient.group_identify` - -```python -group_identify(group_type: str, group_key: str, properties: Optional[Dict[str, Any]] = None, timestamp: Optional[Union[datetime, str]] = None, uuid: Optional[Union[str, UUID]] = None, disable_geoip: Optional[bool] = None, distinct_id: Optional[ID_TYPES] = None) -> Optional[str] -``` - -### `AsyncClient.set` - -```python -set(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str] -``` - -### `AsyncClient.set_once` - -```python -set_once(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str] -``` - -### `AsyncClient(...)` - -```python -__init__(project_api_key: str, host: Optional[str] = None, debug: bool = False, max_queue_size: int = 10000, send: bool = True, on_error: bool = None, flush_at: int = 100, flush_interval: float = 5.0, gzip: bool = False, max_retries: int = 3, timeout: float = 15, thread: int = 1, disabled: bool = False, disable_geoip: bool = True, is_server: bool = True, historical_migration: bool = False, super_properties: Optional[dict[str, Any]] = None, before_send: Optional[dict[str, Any]] = None, log_captured_exceptions: bool = False, project_root: Optional[str] = None, capture_exception_code_variables: bool = False, code_variables_mask_patterns: bool = None, code_variables_ignore_patterns: bool = None, code_variables_mask_url_credentials: bool = None, code_variables_detect_secrets: bool = None, in_app_modules: Optional[list[str]] = None, capture_mode: Optional[Union[CaptureMode, str]] = None, capture_compression: Optional[Union[CaptureCompression, str]] = None, capture_trace_context: bool = False, secret_key: Optional[str] = None, personal_api_key: Optional[str] = None, feature_flags_request_timeout_seconds: float = 3, feature_flags_request_max_retries: int = 1, app_host: Optional[str] = None, email_request_timeout_seconds: float = 15, email_request_max_retries: int = 2) -> None -``` - -### `AsyncClient.shutdown` - -```python -shutdown() -> None -``` - -### `AsyncClient.join` - -```python -join() -> None -``` - -### `AsyncClient.get_remote_config_payload` - -```python -get_remote_config_payload(key: str) -> Optional[Any] -``` - -Fetch and decrypt a remote-config payload without blocking the loop. - -## Module functions - -### `posthog.new_context` - -```python -new_context(fresh: bool = False, capture_exceptions: Optional[bool] = None, client: Optional[Client] = None) -``` - -Create a new context scope that will be active for the duration of the with block. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`fresh` | `bool` | Whether to start with a fresh context (default: False) | `False` -`capture_exceptions` | `Optional[bool]` | Whether to capture exceptions raised within the context. If omitted, defaults to the relevant client's exception autocapture setting. | `None` -`client` | `Optional[Client]` | Optional Posthog client instance to use for this context (default: None) | `None` - -**Examples:** - -```python -from posthog import new_context, tag, capture -with new_context(): - tag("request_id", "123") - capture("event_name", properties={"property": "value"}) -``` - -
-Category - -Contexts - -
- -### `posthog.scoped` - -```python -scoped(fresh = False, capture_exceptions: Optional[bool] = None) -``` - -Decorator that creates a new context for the function. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`fresh` | | Whether to start with a fresh context (default: False) | `False` -`capture_exceptions` | `Optional[bool]` | Whether to capture and track exceptions with posthog error tracking. If omitted, defaults to the global exception autocapture setting. | `None` - -**Examples:** - -```python -from posthog import scoped, tag, capture -@scoped() -def process_payment(payment_id): - tag("payment_id", payment_id) - capture("payment_started") -``` - -
-Category - -Contexts - -
- -### `posthog.set_context_session` - -```python -set_context_session(session_id: str) -``` - -Set the session ID for the current context. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`session_id` | `str` | The session ID to associate with the current context and its children | *required* - -**Examples:** - -```python -from posthog import set_context_session -set_context_session("session_123") -``` - -
-Category - -Contexts - -
- -### `posthog.set_context_device_id` - -```python -set_context_device_id(device_id: str) -``` - -Set the device ID for the current context, associating all feature flag requests -in this or child contexts with the given device ID. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`device_id` | `str` | The device ID to associate with the current context and its children | *required* - -**Examples:** - -```python -from posthog import set_context_device_id -set_context_device_id("device_123") -``` - -
-Category - -Contexts - -
- -### `posthog.identify_context` - -```python -identify_context(distinct_id: str) -``` - -Identify the current context with a distinct ID. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | `str` | The distinct ID to associate with the current context and its children | *required* - -**Examples:** - -```python -from posthog import identify_context -identify_context("user_123") -``` - -
-Category - -Identification - -
- -### `posthog.set_capture_exception_code_variables_context` - -```python -set_capture_exception_code_variables_context(enabled: bool) -``` - -Override code-variable capture for exceptions in the current context. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`enabled` | `bool` | Whether exceptions captured in this context should include local variable values from stack frames. | *required* - -
-Category - -Contexts - -
- -### `posthog.set_code_variables_mask_patterns_context` - -```python -set_code_variables_mask_patterns_context(mask_patterns: list[str]) -``` - -Override code-variable mask patterns for exceptions in the current context. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`mask_patterns` | `list[str]` | Variable-name patterns whose values should be replaced with `***` when code variables are captured. | *required* - -
-Category - -Contexts - -
- -### `posthog.set_code_variables_ignore_patterns_context` - -```python -set_code_variables_ignore_patterns_context(ignore_patterns: list[str]) -``` - -Override code-variable ignore patterns for exceptions in the current context. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`ignore_patterns` | `list[str]` | Variable-name patterns that should be omitted entirely when code variables are captured. | *required* - -
-Category - -Contexts - -
- -### `posthog.set_code_variables_mask_url_credentials_context` - -```python -set_code_variables_mask_url_credentials_context(enabled: bool) -``` - -Whether to scrub credentials embedded in URLs/DSNs (e.g. user:pass@host) from -captured code variables for the current context. - -### `posthog.set_code_variables_detect_secrets_context` - -```python -set_code_variables_detect_secrets_context(enabled: bool) -``` - -Whether to apply entropy-based secret detection as a last-resort redaction of -high-entropy values (API keys, tokens, strong passwords) in captured code -variables for the current context. - -
-Category - -Contexts - -
- -### `posthog.tag` - -```python -tag(name: str, value: Any) -``` - -Add a tag to the current context. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`name` | `str` | The tag key | *required* -`value` | `Any` | The tag value | *required* - -**Examples:** - -```python -from posthog import tag -tag("user_id", "123") -``` - -
-Category - -Contexts - -
- -### `posthog.get_tags` - -```python -get_tags() -> Dict[str, Any] -``` - -Get all tags from the current context. - -**Returns:** - -Type | Description ----- | ----------- -`Dict[str, Any]` | Dict of all tags in the current context - -
-Category - -Contexts - -
- -### `posthog.capture` - -```python -capture(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] -``` - -Capture anything a user does within your system. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`event` | `str` | The event name to specify the event | *required* -`**kwargs` | `Unpack[OptionalCaptureArgs]` | Optional arguments including: distinct_id: Unique identifier for the user properties: Dict of event properties timestamp: When the event occurred. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. uuid: Unique identifier for this event. If omitted, one is generated and returned. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. groups: Dict of group types and IDs flags: A FeatureFlagEvaluations snapshot from evaluate_flags(). The exact values from the snapshot are attached with no extra /flags request. send_feature_flags: Deprecated. Prefer flags=... from evaluate_flags(). When truthy, evaluates flags during capture and attaches them to the event. disable_geoip: Whether to disable GeoIP lookup | `{}` - -
-Details - -Capture allows you to capture anything a user does within your system, which you can later use in PostHog to find patterns in usage, work out which features to improve or where people are giving up. A capture call requires an event name to specify the event. We recommend using [verb] [noun], like `movie played` or `movie updated` to easily identify what your events mean later on. Capture takes a number of optional arguments, which are defined by the `OptionalCaptureArgs` type. - -
- -**Examples:** - -```python -# Context and capture usage -from posthog import new_context, identify_context, tag_context, capture -# Enter a new context (e.g. a request/response cycle, an instance of a background job, etc) -with new_context(): - # Associate this context with some user, by distinct_id - identify_context('some user') - - # Capture an event, associated with the context-level distinct ID ('some user') - capture('movie started') - - # Capture an event associated with some other user (overriding the context-level distinct ID) - capture('movie joined', distinct_id='some-other-user') - - # Capture an event with some properties - capture('movie played', properties={'movie_id': '123', 'category': 'romcom'}) - - # Capture an event with some properties - capture('purchase', properties={'product_id': '123', 'category': 'romcom'}) - # Capture an event with some associated group - capture('purchase', groups={'company': 'id:5'}) - - # Adding a tag to the current context will cause it to appear on all subsequent events - tag_context('some-tag', 'some-value') - - capture('another-event') # Will be captured with `'some-tag': 'some-value'` in the properties dict -``` -```python -# Set event properties -from posthog import capture -capture( - "user_signed_up", - distinct_id="distinct_id_of_the_user", - properties={ - "login_type": "email", - "is_free_trial": "true" - } -) -``` - -### `posthog.capture_ai` - -```python -capture_ai(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] -``` - -Capture an AI event on the dedicated AI capture endpoint. - -Beta: the signature is stable; operational limits (per-event size cap, -batching, endpoint) may change without notice. - -Takes the same arguments and returns the same value as `capture()`: the -event UUID, or None when the event was not admitted (disabled client, or -dropped by `before_send`). The event is delivered on an isolated queue -with its own consumer pool and a higher per-event size cap, posting to -the dedicated AI ingestion endpoint. The payload is sent as given — no -redaction or truncation is applied here. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`event` | `str` | The event name, normally one of the `$ai_*` event names. | *required* -`**kwargs` | `Unpack[OptionalCaptureArgs]` | Same optional arguments as `capture()`. | `{}` - -**Examples:** - -```python -from posthog import capture_ai - -uuid = capture_ai( - "$ai_generation", - distinct_id="user_123", - properties={"$ai_model": "gpt-5"}, -) -``` - -### `posthog.set` - -```python -set(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str] -``` - -Set properties on a user record. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`**kwargs` | `Unpack[OptionalSetArgs]` | Optional arguments including: distinct_id: Unique identifier for the user. Falls back to the context distinct ID; if none exists, this call does nothing. properties: Dict of person properties to set. timestamp: When the properties were set. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. uuid: Unique identifier for this operation. If omitted, one is generated and returned. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. disable_geoip: Whether to disable GeoIP lookup. | `{}` - -
-Details - -This will overwrite previous people property values. Generally operates similar to `capture`, with distinct_id being an optional argument, defaulting to the current context's distinct ID. If there is no context-level distinct ID, and no override distinct_id is passed, this function will do nothing. Context tags are folded into $set properties, so tagging the current context and then calling `set` will cause those tags to be set on the user (unlike capture, which causes them to just be set on the event). - -
- -**Examples:** - -```python -# Set person properties -from posthog import set -set(distinct_id='distinct_id', properties={'name': 'Max Hedgehog'}) -``` - -### `posthog.set_once` - -```python -set_once(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str] -``` - -Set properties on a user record, only if they do not yet exist. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`**kwargs` | `Unpack[OptionalSetArgs]` | Optional arguments including: distinct_id: Unique identifier for the user. Falls back to the context distinct ID; if none exists, this call does nothing. properties: Dict of person properties to set only once. timestamp: When the properties were set. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. uuid: Unique identifier for this operation. If omitted, one is generated and returned. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. disable_geoip: Whether to disable GeoIP lookup. | `{}` - -
-Details - -This will not overwrite previous people property values, unlike `set`. Otherwise, operates in an identical manner to `set`. - -
- -**Examples:** - -```python -# Set property once -from posthog import set_once -set_once(distinct_id='distinct_id', properties={'initial_url': '/blog'}) - -``` - -### `posthog.group_identify` - -```python -group_identify(group_type: str, group_key: str, properties: Optional[Dict[str, Any]] = None, timestamp: Optional[Union[datetime.datetime, str]] = None, uuid: Optional[str] = None, disable_geoip: Optional[bool] = None, distinct_id: Optional[ID_TYPES] = None) -> Optional[str] -``` - -Set properties on a group. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`group_type` | `str` | Type of your group. Required - the call is dropped with a warning if it is missing or empty. | *required* -`group_key` | `str` | Unique identifier of the group. Required - the call is dropped with a warning if it is missing or empty. | *required* -`properties` | `Optional[Dict[str, Any]]` | Properties to set on the group | `None` -`timestamp` | `Optional[Union[datetime, str]]` | Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | `None` -`uuid` | `Optional[str]` | Optional UUID for the event | `None` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup | `None` -`distinct_id` | `Optional[ID_TYPES]` | Optional distinct ID of the user performing the action | `None` - -**Examples:** - -```python -# Group identify -from posthog import group_identify -group_identify('company', 'company_id_in_your_db', { - 'name': 'Awesome Inc.', - 'employees': 11 -}) -``` - -### `posthog.alias` - -```python -alias(previous_id: ID_TYPES, distinct_id: str, timestamp: Optional[Union[datetime.datetime, str]] = None, uuid: Optional[str] = None, disable_geoip: Optional[bool] = None) -> Optional[str] -``` - -Associate user behaviour before and after they e.g. register, login, or perform some other identifying action. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`previous_id` | `ID_TYPES` | The unique ID of the user before | *required* -`distinct_id` | `str` | The current unique id | *required* -`timestamp` | `Optional[Union[datetime, str]]` | Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | `None` -`uuid` | `Optional[str]` | Optional UUID for the event | `None` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup | `None` - -
-Details - -To marry up whatever a user does before they sign up or log in with what they do after you need to make an alias call. This will allow you to answer questions like "Which marketing channels leads to users churning after a month?" or "What do users do on our website before signing up?". Particularly useful for associating user behaviour before and after they e.g. register, login, or perform some other identifying action. - -
- -**Examples:** - -```python -# Alias user -from posthog import alias -alias(previous_id='distinct_id', distinct_id='alias_id') -``` - -### `posthog.capture_exception` - -```python -capture_exception(exception: Optional[ExceptionArg] = None, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] -``` - -Capture exceptions that happen in your code. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`exception` | `Optional[ExceptionArg]` | The exception to capture. If not provided, the current exception is captured via `sys.exc_info()` | `None` -`**kwargs` | `Unpack[OptionalCaptureArgs]` | Optional capture arguments including distinct_id, properties, timestamp, uuid, groups, flags, send_feature_flags, and disable_geoip. Overriding reserved exception properties through `properties` is deprecated and will stop working in the next major version. | `{}` - -
-Details - -Capture exception is idempotent - if it is called twice with the same exception instance, only a occurrence will be tracked in posthog. This is because, generally, contexts will cause exceptions to be captured automatically. However, to ensure you track an exception, if you catch and do not re-raise it, capturing it manually is recommended, unless you are certain it will have crossed a context boundary (e.g. by existing a `with posthog.new_context():` block already). If the passed exception was raised and caught, the captured stack trace will consist of every frame between where the exception was raised and the point at which it is captured (the "traceback"). If the passed exception was never raised, e.g. if you call `posthog.capture_exception(ValueError("Some Error"))`, the stack trace captured will be the full stack trace at the moment the exception was captured. Note that heavy use of contexts will lead to truncated stack traces, as the exception will be captured by the context entered most recently, which may not be the point you catch the exception for the final time in your code. It's recommended to use contexts sparingly, for this reason. `capture_exception` takes the same set of optional arguments as `capture`. - -
- -**Examples:** - -```python -# Capture exception -from posthog import capture_exception -try: - risky_operation() -except Exception as e: - capture_exception(e) -``` - -### `posthog.feature_enabled` - -```python -feature_enabled(key: str, distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[bool] -``` - -Use feature flags to enable or disable features for users. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The feature flag key | *required* -`distinct_id` | `ID_TYPES` | The user's distinct ID | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | Groups mapping | `None` -`person_properties` | `Optional[Dict[str, Any]]` | Person properties | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties | `None` -`only_evaluate_locally` | `bool` | Whether to evaluate only locally | `False` -`send_feature_flag_events` | `bool` | Whether to send feature flag events | `True` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup | `None` -`device_id` | `Optional[str]` | Optional device ID override for experience-continuity flags | `None` - -
-Details - -You can call `posthog.load_feature_flags()` before to make sure you're not doing unexpected requests. - -
- -**Examples:** - -```python -# Boolean feature flag -from posthog import feature_enabled, get_feature_flag_payload -is_my_flag_enabled = feature_enabled('flag-key', 'distinct_id_of_your_user') -if is_my_flag_enabled: - matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user') -``` - -### `posthog.get_feature_flag` - -```python -get_feature_flag(key: str, distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[FlagValue] -``` - -Get feature flag variant for users. Used with experiments. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The feature flag key | *required* -`distinct_id` | `ID_TYPES` | The user's distinct ID | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | Groups mapping from group type to group key | `None` -`person_properties` | `Optional[Dict[str, Any]]` | Person properties | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties in format { group_type_name: { group_properties } } | `None` -`only_evaluate_locally` | `bool` | Whether to evaluate only locally | `False` -`send_feature_flag_events` | `bool` | Whether to send feature flag events | `True` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup | `None` -`device_id` | `Optional[str]` | Optional device ID override for experience-continuity flags | `None` - -
-Details - -`groups` are a mapping from group type to group key. So, if you have a group type of "organization" and a group key of "5", you would pass groups={"organization": "5"}. `group_properties` take the format: { group_type_name: { group_properties } }. So, for example, if you have the group type "organization" and the group key "5", with the properties name, and employee count, you'll send these as: group_properties={"organization": {"name": "PostHog", "employees": 11}}. - -
- -**Examples:** - -```python -# Multivariate feature flag -from posthog import get_feature_flag, get_feature_flag_payload -enabled_variant = get_feature_flag('flag-key', 'distinct_id_of_your_user') -if enabled_variant == 'variant-key': - matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user') -``` - -### `posthog.get_all_flags` - -```python -get_all_flags(distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None, flag_keys_to_evaluate: Optional[list[str]] = None) -> Optional[dict[str, FlagValue]] -``` - -Get all flags for a given user. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | `ID_TYPES` | The user's distinct ID | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | Groups mapping | `None` -`person_properties` | `Optional[Dict[str, Any]]` | Person properties | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties | `None` -`only_evaluate_locally` | `bool` | Whether to evaluate only locally | `False` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup | `None` -`device_id` | `Optional[str]` | Optional device ID override for experience-continuity flags | `None` -`flag_keys_to_evaluate` | `Optional[list[str]]` | Optional list of flag keys to evaluate (evaluates all if None) | `None` - -
-Details - -Flags are key-value pairs where the key is the flag key and the value is the flag variant, or True, or False. - -
- -**Examples:** - -```python -# All flags for user -from posthog import get_all_flags -get_all_flags('distinct_id_of_your_user') -``` - -### `posthog.get_feature_flag_result` - -```python -get_feature_flag_result(key: str, distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[FeatureFlagResult] -``` - -Get a FeatureFlagResult object which contains the flag result and payload. - -This method evaluates a feature flag and returns a FeatureFlagResult object containing: -- enabled: Whether the flag is enabled -- variant: The variant value if the flag has variants -- payload: The payload associated with the flag (automatically deserialized from JSON) -- key: The flag key -- reason: Why the flag was enabled/disabled - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The feature flag key. | *required* -`distinct_id` | `ID_TYPES` | The user's distinct ID. | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | Mapping of group type to group key. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | Person properties to use for evaluation. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties keyed by group type. | `None` -`only_evaluate_locally` | `bool` | Whether to evaluate only locally. | `False` -`send_feature_flag_events` | `bool` | Whether to send a $feature_flag_called event. | `True` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup. | `None` -`device_id` | `Optional[str]` | Optional device ID override for experience-continuity flags. | `None` - -Example: -```python -result = posthog.get_feature_flag_result('beta-feature', 'distinct_id') -if result and result.enabled: - # Use the variant and payload - print(f"Variant: {result.variant}") - print(f"Payload: {result.payload}") -``` - -### `posthog.get_feature_flag_payload` - -```python -get_feature_flag_payload(key: str, distinct_id: ID_TYPES, match_value: Optional[FlagValue] = None, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[object] -``` - -Get the payload associated with a feature flag value. - -Deprecated for new code. Prefer `evaluate_flags()` and -`flags.get_flag_payload(key)` so flag evaluation happens once per request. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The feature flag key. | *required* -`distinct_id` | `ID_TYPES` | The user's distinct ID. | *required* -`match_value` | `Optional[FlagValue]` | Optional flag value to use when selecting a payload. | `None` -`groups` | `Optional[Mapping[str, Union[str, int]]]` | Mapping of group type to group key. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | Person properties to use for evaluation. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties keyed by group type. | `None` -`only_evaluate_locally` | `bool` | Whether to evaluate only locally. | `False` -`send_feature_flag_events` | `bool` | Whether to send a $feature_flag_called event. | `True` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup. | `None` -`device_id` | `Optional[str]` | Optional device ID override for experience-continuity flags. | `None` - -**Returns:** - -Type | Description ----- | ----------- -`Optional[object]` | The payload associated with the matched feature flag value, or None. -`Optional[object]` | This function returns the payload only, not the FeatureFlagResult wrapper -`Optional[object]` | used internally to compute it. - -### `posthog.get_remote_config_payload` - -```python -get_remote_config_payload(key: str) -``` - -Get the payload for a remote config feature flag. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The key of the feature flag | *required* - -**Returns:** - -Type | Description ----- | ----------- - | The payload associated with the feature flag. If payload is encrypted, the return value will be decrypted - -
-Note - -Requires secret_key to be set for authentication - -
- -### `posthog.get_all_flags_and_payloads` - -```python -get_all_flags_and_payloads(distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None, flag_keys_to_evaluate: Optional[list[str]] = None) -> FlagsAndPayloads -``` - -Get all feature flag values and payloads for a user. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | `ID_TYPES` | The user's distinct ID. | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | Mapping of group type to group key. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | Person properties to use for evaluation. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties keyed by group type. | `None` -`only_evaluate_locally` | `bool` | Whether to evaluate only locally. | `False` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup. | `None` -`device_id` | `Optional[str]` | Optional device ID override for experience-continuity flags. | `None` -`flag_keys_to_evaluate` | `Optional[list[str]]` | Optional list of flag keys to evaluate. Evaluates all flags when omitted. | `None` - -**Returns:** - -Type | Description ----- | ----------- -`FlagsAndPayloads` | A dict with `featureFlags` and `featureFlagPayloads` entries. - -### `posthog.evaluate_flags` - -```python -evaluate_flags(distinct_id: Optional[ID_TYPES] = None, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, flag_keys: Optional[list[str]] = None, device_id: Optional[str] = None) -> FeatureFlagEvaluations -``` - -Evaluate all feature flags for a user in a single call and return a -`FeatureFlagEvaluations` snapshot. Branch on `.is_enabled()` / -`.get_flag()` and pass the same snapshot to `capture()` via the -`flags` option so events carry the exact flag values the code branched on. - -Prefer this over repeated `get_feature_flag()` calls and over -`capture(send_feature_flags=True)` — it consolidates flag evaluation into -a single `/flags` request per incoming request. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | `Optional[ID_TYPES]` | The user's distinct ID. If `None`, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot. | `None` -`groups` | `Optional[Mapping[str, Union[str, int]]]` | Mapping of group type to group key. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | Person properties to use for evaluation. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties keyed by group type. | `None` -`only_evaluate_locally` | `bool` | If `True`, never fall back to remote evaluation and omit flags that cannot be evaluated locally. The snapshot's `unresolved_flags` lists them with a reason. | `False` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup. | `None` -`flag_keys` | `Optional[list[str]]` | Optional list that scopes local evaluation, the underlying `/flags` request, and the returned snapshot. When omitted or `None`, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per `evaluate_flags` call unless `only_evaluate_locally` is `True`. If the server also does not know the key, it is omitted from the snapshot. | `None` -`device_id` | `Optional[str]` | Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes. | `None` - -**Examples:** - -```python -from posthog import evaluate_flags, capture -flags = evaluate_flags("user_123", person_properties={"plan": "enterprise"}) -if flags.is_enabled("new-dashboard"): - render_new_dashboard() -capture("page_viewed", distinct_id="user_123", flags=flags) -``` - -### `posthog.feature_flag_definitions` - -```python -feature_flag_definitions() -``` - -Returns loaded feature flags. - -
-Details - -Returns loaded feature flags, if any. Helpful for debugging what flag information you have loaded. - -
- -**Examples:** - -```python -from posthog import feature_flag_definitions -definitions = feature_flag_definitions() -``` - -### `posthog.get_feature_flag_evaluation_runtime` - -```python -get_feature_flag_evaluation_runtime(key: str) -> Optional[FeatureFlagEvaluationRuntime] -``` - -Return where a locally loaded feature flag is meant to be evaluated. - -
-Details - -Reads the `evaluation_runtime` each flag definition carries, so no extra -request is made. Returns `None` when local evaluation has not loaded a -definition for this key. A definition that carries no runtime reports -`FeatureFlagEvaluationRuntime.ALL`, the default PostHog applies. - -
- -**Examples:** - -```python -from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_evaluation_runtime -runtime = get_feature_flag_evaluation_runtime("my-flag") -``` - -
-Category - -Feature flags - -
- -### `posthog.get_feature_flag_keys_by_evaluation_runtime` - -```python -get_feature_flag_keys_by_evaluation_runtime(evaluation_runtime: Union[FeatureFlagEvaluationRuntime, str]) -> list[str] -``` - -Return the keys of locally loaded flags that a runtime can evaluate. - -
-Details - -A flag set to `FeatureFlagEvaluationRuntime.ALL` suits either runtime, so -it is returned for `CLIENT` and for `SERVER`. Use this to decide which -flags to hand to a browser when a backend serves flags to its own -frontend. - -
- -**Examples:** - -```python -from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_keys_by_evaluation_runtime -client_keys = get_feature_flag_keys_by_evaluation_runtime(FeatureFlagEvaluationRuntime.CLIENT) -``` - -
-Category - -Feature flags - -
- -### `posthog.load_feature_flags` - -```python -load_feature_flags() -``` - -Load feature flag definitions from PostHog. - -**Examples:** - -```python -from posthog import load_feature_flags -load_feature_flags() -``` - -### `posthog.flush` - -```python -flush(timeout_seconds: Optional[float] = 10) -> None -``` - -Attempt immediate delivery of queued events and ended spans, keeping the global client reusable. - -Call `flush()` before a short-lived request or serverless invocation returns -when using the shared module-level client. Use `shutdown()` only for final -process cleanup or a client that will not be reused. Flush does not stop the -background workers or feature flag poller, and does not end open spans. - -Returning does not guarantee server receipt: pending work can remain after a -timeout or span export failure, and failed events may be dropped by consumers. -Calls made directly from SDK callbacks such as `on_error` are deferred -rather than blocking the worker that invoked the callback. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`timeout_seconds` | `Optional[float]` | Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass `None` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently, waiting up to this many seconds for an in-flight flush before starting a fresh drain budget. A lock wait timeout leaves the in-flight flush to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. The last span request is not cut short and is bounded by the client's `timeout` in seconds, so this is not a strict wall-clock limit for the whole call. With `None`, a retriable span failure is left for a later flush rather than retried within this call. | `10` - -**Examples:** - -```python -import posthog -posthog.capture('event_name', distinct_id='user_id') -# Before a serverless invocation returns; keep the global client open. -posthog.flush(timeout_seconds=10) -``` - -### `posthog.join` - -```python -join() -> None -``` - -Attempt to process queued events and stop the client's background workers. Use `shutdown()` directly in most cases. - -Failed or undrainable events may be dropped and reported through logging or -`on_error`; returning does not guarantee server receipt. Lifecycle cleanup -is attempted once, and cleanup failures are logged without retry. - -**Examples:** - -```python -from posthog import join -join() -``` - -### `posthog.start_span` - -```python -start_span(name: str, *, kind: Optional[str] = None, attributes: Optional[Mapping[str, Any]] = None, parent: Union[Span, str, None] = None, tracestate: Optional[str] = None, start_time: Union[datetime.datetime, float, None] = None) -> Span -``` - -Start a span for distributed tracing. Alpha. - -Returns a span handle. Use it as a context manager to make it the active -span for the block and end it on exit (recording a raised exception on the -way out); or call `end()` yourself for a span that cannot wrap a block. -Spans started inside the block nest under it automatically. Always returns -a usable handle, even when tracing is off, so calling code never branches. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`name` | `str` | A low-cardinality operation name, e.g. `GET /users/:id`. Variable values belong in attributes, not the name. | *required* -`kind` | `Optional[str]` | `internal` (default), `server`, `client`, `producer` or `consumer`. | `None` -`attributes` | `Optional[Mapping[str, Any]]` | Initial attributes. | `None` -`parent` | `Union[Span, str, None]` | A span handle, or an inbound W3C `traceparent` header value to continue a remote trace. Defaults to the active span. | `None` -`tracestate` | `Optional[str]` | The inbound `tracestate` header accompanying a `traceparent` string `parent`; preserved and propagated. | `None` -`start_time` | `Union[datetime, float, None]` | A `datetime` or epoch seconds, to backdate the span. | `None` - -**Examples:** - -```python -import posthog -posthog.traces = {"service_name": "checkout-api"} - -with posthog.start_span("POST /checkout", parent=request.headers.get("traceparent")) as span: - span.set_attribute("plan", user.plan) - with posthog.start_span("db.query", kind="client"): - ... - outgoing_headers = {"traceparent": span.traceparent()} -``` - -### `posthog.get_active_span` - -```python -get_active_span() -> Optional[Span] -``` - -The span that is active in the current context, or `None`. Alpha. - -Only entering a span (`with posthog.start_span(...) as span:`) makes it -active; a span started manually is not. Use it to propagate the trace to -the next service: `span.traceparent()` is the header value. - -**Examples:** - -```python -span = posthog.get_active_span() -if span is not None: - headers["traceparent"] = span.traceparent() -``` - -### `posthog.shutdown` - -```python -shutdown() -> None -``` - -Attempt final delivery and permanently shut down the global client. - -Call this for final process cleanup, not at the end of each request or -serverless invocation that shares the module-level client. Use `flush()` -before returning from those invocations instead. Shutdown closes capture -lanes, stops background workers and the feature flag poller, and tears down -integrations. The global client is not recreated automatically after shutdown. - -This normally blocks until queued events have been attempted and cleanup -finishes. Failed or undrainable events may be dropped and reported through -logging or `on_error`; returning does not guarantee server receipt. Event -draining has no timeout. Queued spans get a final flush with up to 30 seconds -waiting for an in-flight flush, then a fresh 30-second drain budget; the last -HTTP request is not cut short. Unsent queued spans and still-open spans are -discarded with a warning. -Lifecycle cleanup is attempted once, and cleanup failures are logged without -retry. Calls made directly from SDK callbacks such as `on_error` are deferred -to avoid deadlocking the worker. If blocking completion is required, signal an application-owned -thread, return from the callback, and call `shutdown()` from that thread. -Do not wait inside a callback for another thread or task calling a lifecycle -method. - -**Examples:** - -```python -from posthog import shutdown -shutdown() -``` - -### `posthog.setup` - -```python -setup() -> Client -``` - -Create or return the global PostHog client configured by module settings. - -Most applications should either instantiate `Posthog` directly or set -`posthog.api_key`/other module settings before calling top-level helpers. -`setup()` is called automatically by global APIs such as `capture()`. - -**Returns:** - -Type | Description ----- | ----------- -`Client` | The global `Client` instance. If both `api_key` and -`Client` | `project_api_key` are missing or blank, the client is disabled and -`Client` | module-level calls become no-ops. - -## Types - -### `OptionalCaptureArgs` - -Bases: `TypedDict` - -Optional arguments for the capture method. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | | Unique identifier for the person associated with this event. If not set, the context distinct_id is used, if available, otherwise a UUID is generated, and the event is marked as personless. Setting context-level distinct_id's is recommended. | -`properties` | | Dictionary of properties to track with the event | -`timestamp` | | When the event occurred (defaults to current time). UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | -`uuid` | | Unique identifier for this specific event. If not provided, one is generated. The event UUID is returned, so you can correlate it with actions in your app (like showing users an error ID if you capture an exception). If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | -`groups` | | Group identifiers to associate with this event (format: {group_type: group_key}) | -`flags` | | A `FeatureFlagEvaluations` snapshot from `evaluate_flags()`. The exact flag values from the snapshot are attached to the event with no additional network call — prefer this over `send_feature_flags`. | -`send_feature_flags` | | Deprecated — prefer `flags` with a `FeatureFlagEvaluations` snapshot. Whether to include currently active feature flags in the event properties. Can be a boolean or a SendFeatureFlagsOptions object. Defaults to False. Fires a hidden `/flags` request on capture and may return different values than the ones the code branched on. | -`disable_geoip` | | Whether to disable GeoIP lookup for this event. Defaults to False. | - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`disable_geoip` | `NotRequired[Optional[bool]]` | -`distinct_id` | `NotRequired[Optional[ID_TYPES]]` | -`flags` | `NotRequired[Optional[FeatureFlagEvaluations]]` | -`groups` | `NotRequired[Optional[Dict[str, str]]]` | -`properties` | `NotRequired[Optional[Dict[str, Any]]]` | -`send_feature_flags` | `NotRequired[Optional[Union[bool, SendFeatureFlagsOptions]]]` | -`timestamp` | `NotRequired[Optional[Union[datetime, str]]]` | -`uuid` | `NotRequired[Optional[Union[str, UUID]]]` | - -#### `posthog.args.OptionalCaptureArgs.disable_geoip` - -```python -disable_geoip: NotRequired[Optional[bool]] -``` - -#### `posthog.args.OptionalCaptureArgs.distinct_id` - -```python -distinct_id: NotRequired[Optional[ID_TYPES]] -``` - -#### `posthog.args.OptionalCaptureArgs.flags` - -```python -flags: NotRequired[Optional[FeatureFlagEvaluations]] -``` - -#### `posthog.args.OptionalCaptureArgs.groups` - -```python -groups: NotRequired[Optional[Dict[str, str]]] -``` - -#### `posthog.args.OptionalCaptureArgs.properties` - -```python -properties: NotRequired[Optional[Dict[str, Any]]] -``` - -#### `posthog.args.OptionalCaptureArgs.send_feature_flags` - -```python -send_feature_flags: NotRequired[Optional[Union[bool, SendFeatureFlagsOptions]]] -``` - -#### `posthog.args.OptionalCaptureArgs.timestamp` - -```python -timestamp: NotRequired[Optional[Union[datetime, str]]] -``` - -#### `posthog.args.OptionalCaptureArgs.uuid` - -```python -uuid: NotRequired[Optional[Union[str, UUID]]] -``` - -### `FeatureFlagEvaluations` - -```python -FeatureFlagEvaluations(host: _FeatureFlagEvaluationsHost, distinct_id: str, flags: Dict[str, _EvaluatedFlagRecord], groups: Optional[Mapping[str, Union[str, int]]] = None, disable_geoip: Optional[bool] = None, request_id: Optional[str] = None, evaluated_at: Optional[int] = None, errors_while_computing: bool = False, quota_limited: bool = False, minimal_flag_called_events: bool = False, accessed: Optional[Set[str]] = None, unresolved_flags: Optional[Mapping[str, UnresolvedFlagReason]] = None) -> None -``` - -A point-in-time snapshot of feature flag evaluations for a single distinct_id. - -Returned by `Client.evaluate_flags` — branch on `is_enabled` / -`get_flag` and pass the same object to `Client.capture` via the -`flags` option so the captured event carries the exact flag values the code -branched on. - -Example:: - - flags = posthog.evaluate_flags(distinct_id, person_properties={"plan": "enterprise"}) - if flags.is_enabled("new-dashboard"): - render_new_dashboard() - posthog.capture("page_viewed", distinct_id=distinct_id, flags=flags) - -To narrow the set of flags that get attached to a captured event, use the in-memory -helpers `only` and `only_accessed`. To narrow the set of flags requested -from the server in the first place, pass `flag_keys` to `Client.evaluate_flags`. - -**Functions:** - -Name | Description ----- | ----------- -`get_flag` | Return the flag value. Fires `$feature_flag_called` on first access. -`get_flag_payload` | Return the payload associated with a flag. -`is_enabled` | Return whether the flag is enabled. Fires `$feature_flag_called` on the -`only` | Return a filtered copy containing only flags with the given keys. Keys that -`only_accessed` | Return a filtered copy containing only flags accessed via `is_enabled` - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`keys` | `List[str]` | Return the flag keys that are part of this evaluation. -`unresolved_flags` | `Mapping[str, UnresolvedFlagReason]` | Return a read-only mapping of flag key to the reason local evaluation - -Internal — instances are created by the SDK via `Client.evaluate_flags()`. - -#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.get_flag` - -```python -get_flag(key: str, *, send_feature_flag_events: bool = True) -> Optional[FlagValue] -``` - -Return the flag value. Fires `$feature_flag_called` on first access. - -Returns the variant string for multivariate flags, `True` for enabled flags -without a variant, `False` for disabled flags, and `None` for flags that -were not returned by the evaluation. - -Pass `send_feature_flag_events=False` to skip the event for this read, as -with `is_enabled`. - -#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.get_flag_payload` - -```python -get_flag_payload(key: str) -> Optional[Any] -``` - -Return the payload associated with a flag. - -Does not count as an access for `only_accessed` and does not fire any event. - -#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.is_enabled` - -```python -is_enabled(key: str, default_value: bool = False, *, send_feature_flag_events: bool = True) -> bool -``` - -Return whether the flag is enabled. Fires `$feature_flag_called` on the -first access per (distinct_id, flag, value) tuple, deduped via the SDK's cache. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The feature flag key. | *required* -`default_value` | `bool` | Returned when the flag has no value in this evaluation — the key was not part of the evaluated set, or the evaluation itself came back empty (failed `/flags` request, quota limit, or no resolvable `distinct_id`). Defaults to `False`. | `False` -`send_feature_flag_events` | `bool` | Whether this read sends `$feature_flag_called`. When `False`, no event is sent and the dedupe cache is left alone, but the key still counts as accessed for `only_accessed`. | `True` - -A flag that has a value always wins over `default_value`, including a -disabled flag, which returns `False` even when `default_value=True`. - -#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.keys` - -```python -keys: List[str] -``` - -Return the flag keys that are part of this evaluation. - -#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.only` - -```python -only(keys: List[str]) -> FeatureFlagEvaluations -``` - -Return a filtered copy containing only flags with the given keys. Keys that -are not present in the evaluation are dropped and logged as a warning. - -#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.only_accessed` - -```python -only_accessed() -> FeatureFlagEvaluations -``` - -Return a filtered copy containing only flags accessed via `is_enabled` -or `get_flag` before this call. - -Order-dependent: if nothing has been accessed yet, the returned snapshot is -empty. The method honors its name — pre-access if you want a populated result. - -#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.unresolved_flags` - -```python -unresolved_flags: Mapping[str, UnresolvedFlagReason] -``` - -Return a read-only mapping of flag key to the reason local evaluation -left it without a value. - -A flag is listed when it has a loaded local definition, was in the requested -scope, was inconclusive locally, and no remote value filled the gap. Listed -flags are not part of `keys`, and reading one reports the -`local_evaluation_inconclusive` error instead of `flag_missing`. Reading -this mapping does not count as an access and does not fire any event. -Snapshots returned by `only` and `only_accessed`, and by -`AsyncClient.evaluate_flags()`, which evaluates only remotely, carry no -entries. - -### `FeatureFlagResult` - -```python -FeatureFlagResult(key: str, enabled: bool, variant: Optional[str], payload: Optional[Any], reason: Optional[str]) -> None -``` - -The result of calling a feature flag which includes the flag result, variant, and payload. - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`key` | `str` | The unique identifier of the feature flag. -`enabled` | `bool` | Whether the feature flag is enabled for the current context. -`variant` | `Optional[str]` | The variant value if the flag is enabled and has variants, None otherwise. -`payload` | `Optional[Any]` | Additional data associated with the feature flag, if any. -`reason` | `Optional[str]` | A description of why the flag was enabled or disabled, if available. - -**Functions:** - -Name | Description ----- | ----------- -`from_flag_details` | Create a FeatureFlagResult from a FeatureFlag object. -`from_value_and_payload` | Creates a FeatureFlagResult from a flag value and payload. -`get_value` | Returns the value of the flag. This is the variant if it exists, otherwise the enabled value. - -#### `posthog.types.FeatureFlagResult.enabled` - -```python -enabled: bool -``` - -#### `posthog.types.FeatureFlagResult.from_flag_details` - -```python -from_flag_details(details: Union[FeatureFlag, None], override_match_value: Optional[FlagValue] = None) -> FeatureFlagResult | None -``` - -Create a FeatureFlagResult from a FeatureFlag object. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`details` | `Union[FeatureFlag, None]` | The FeatureFlag object to convert. | *required* -`override_match_value` | `Optional[FlagValue]` | If provided, this value will be used to populate the enabled and variant fields instead of the values from the FeatureFlag. | `None` - -**Returns:** - -Type | Description ----- | ----------- -`FeatureFlagResult \| None` | FeatureFlagResult | None: A new FeatureFlagResult instance, or None if details is None. - -#### `posthog.types.FeatureFlagResult.from_value_and_payload` - -```python -from_value_and_payload(key: str, value: Union[FlagValue, None], payload: Any) -> Union[FeatureFlagResult, None] -``` - -Creates a FeatureFlagResult from a flag value and payload. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The unique identifier of the feature flag. | *required* -`value` | `Union[FlagValue, None]` | The value of the flag (string variant or boolean). | *required* -`payload` | `Any` | Additional data associated with the feature flag. | *required* - -**Returns:** - -Type | Description ----- | ----------- -`Union[FeatureFlagResult, None]` | Union[FeatureFlagResult, None]: A new FeatureFlagResult instance, or None if value is None. - -#### `posthog.types.FeatureFlagResult.get_value` - -```python -get_value() -> FlagValue -``` - -Returns the value of the flag. This is the variant if it exists, otherwise the enabled value. -This is the value we report as `$feature_flag_response` in the `$feature_flag_called` event. - -**Returns:** - -Name | Type | Description ----- | ---- | ----------- -`FlagValue` | `FlagValue` | Either a string variant or boolean value representing the flag's state. - -#### `posthog.types.FeatureFlagResult.key` - -```python -key: str -``` - -#### `posthog.types.FeatureFlagResult.payload` - -```python -payload: Optional[Any] -``` - -#### `posthog.types.FeatureFlagResult.reason` - -```python -reason: Optional[str] -``` - -#### `posthog.types.FeatureFlagResult.variant` - -```python -variant: Optional[str] -``` - -### `FeatureFlag` - -```python -FeatureFlag(key: str, enabled: bool, variant: Optional[str], reason: Optional[FlagReason], metadata: Union[FlagMetadata, LegacyFlagMetadata]) -> None -``` - -Detailed feature flag evaluation returned by the flags API. - -`enabled`, `variant` and `metadata.payload` are the value rendered the way -responses before `/flags?v=4` carried it, which the legacy getters return. - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`key` | `str` | Feature flag key. -`enabled` | `bool` | Whether the flag is enabled for the evaluated user or group. -`variant` | `Optional[str]` | Variant key for multivariate flags, otherwise `None`. -`reason` | `Optional[FlagReason]` | Optional reason metadata explaining the result. -`metadata` | `Union[FlagMetadata, LegacyFlagMetadata]` | Payload and other metadata returned by the API. - -**Functions:** - -Name | Description ----- | ----------- -`from_json` | Parse one flag record of a `/flags` response. -`from_value_and_payload` | Build a flag from its value and payload. A string value is a variant, so the flag is enabled. -`get_value` | Return the variant for a multivariate flag, otherwise whether the flag is enabled. - -#### `posthog.types.FeatureFlag.enabled` - -```python -enabled: bool -``` - -#### `posthog.types.FeatureFlag.from_json` - -```python -from_json(resp: Any) -> FeatureFlag -``` - -Parse one flag record of a `/flags` response. - -A record with a `value` member is a `/flags?v=4` record. A record -without one comes from an older server: its value is `variant`, or -`enabled` when there is no variant, and its config version is `1`. - -#### `posthog.types.FeatureFlag.from_value_and_payload` - -```python -from_value_and_payload(key: str, value: FlagValue, payload: Any) -> FeatureFlag -``` - -Build a flag from its value and payload. A string value is a variant, so the flag is enabled. - -#### `posthog.types.FeatureFlag.get_value` - -```python -get_value() -> FlagValue -``` - -Return the variant for a multivariate flag, otherwise whether the flag is enabled. - -#### `posthog.types.FeatureFlag.key` - -```python -key: str -``` - -#### `posthog.types.FeatureFlag.metadata` - -```python -metadata: Union[FlagMetadata, LegacyFlagMetadata] -``` - -#### `posthog.types.FeatureFlag.reason` - -```python -reason: Optional[FlagReason] -``` - -#### `posthog.types.FeatureFlag.variant` - -```python -variant: Optional[str] -``` - -### `FlagsAndPayloads` - -Bases: `TypedDict` - -Feature flag values and payloads keyed by feature flag key. - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`featureFlags` | `Optional[dict[str, FlagValue]]` | Each flag's value, keyed by flag key. -`featureFlagPayloads` | `Optional[dict[str, Any]]` | Each flag's payload, keyed by flag key. - -#### `posthog.types.FlagsAndPayloads.featureFlagPayloads` - -```python -featureFlagPayloads: Optional[dict[str, Any]] -``` - -#### `posthog.types.FlagsAndPayloads.featureFlags` - -```python -featureFlags: Optional[dict[str, FlagValue]] -``` - -### `UnresolvedFlagReason` - -Bases: `str`, `Enum` - -Why local evaluation left a flag without a value. - -Reported by `FeatureFlagEvaluations.unresolved_flags`. Inheriting from -`str` keeps the members directly comparable to their string values. When more -than one cause applies, the first reason in the order below is reported. - -
-Values - -EXPERIENCE_CONTINUITY: The flag has experience continuity enabled, which - local evaluation never resolves. Change the flag to fix it. -UNSUPPORTED_DEFINITION: The flag uses something the local evaluator does not - support, such as a static cohort, an unknown operator, a malformed value, - a dependency with no loaded definition or a circular dependency. Change - the flag or upgrade the SDK to fix it. -UNRESOLVED_DEPENDENCY: A flag this flag depends on has a loaded definition - but was unresolved. -MISSING_CONTEXT: The call did not pass a property, group property or device - id the flag needs, or passed it in an unusable form. Pass it to fix it. - -
- -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`EXPERIENCE_CONTINUITY` | | -`MISSING_CONTEXT` | | -`UNRESOLVED_DEPENDENCY` | | -`UNSUPPORTED_DEFINITION` | | - -#### `posthog.types.UnresolvedFlagReason.EXPERIENCE_CONTINUITY` - -```python -EXPERIENCE_CONTINUITY = 'experience_continuity' -``` - -#### `posthog.types.UnresolvedFlagReason.MISSING_CONTEXT` - -```python -MISSING_CONTEXT = 'missing_context' -``` - -#### `posthog.types.UnresolvedFlagReason.UNRESOLVED_DEPENDENCY` - -```python -UNRESOLVED_DEPENDENCY = 'unresolved_dependency' -``` - -#### `posthog.types.UnresolvedFlagReason.UNSUPPORTED_DEFINITION` - -```python -UNSUPPORTED_DEFINITION = 'unsupported_definition' -``` - -### `InconclusiveMatchError` - -```python -InconclusiveMatchError(*args, reason: Optional[UnresolvedFlagReason] = None) -``` - -Bases: `Exception` - -Raised when local evaluation can't determine a flag's value. `reason` says -why, as reported by `FeatureFlagEvaluations.unresolved_flags`. - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`reason` | `UnresolvedFlagReason` | Why local evaluation couldn't resolve the flag. - -#### `posthog.feature_flags.InconclusiveMatchError.reason` - -```python -reason: UnresolvedFlagReason = UnresolvedFlagReason.UNSUPPORTED_DEFINITION -``` - -### `RequiresServerEvaluation` - -```python -RequiresServerEvaluation(*args, reason: Optional[UnresolvedFlagReason] = None) -``` - -Bases: `Exception` - -Raised when feature flag evaluation requires server-side data that is not -available locally (e.g., static cohorts). - -This error should propagate immediately to trigger API fallback, unlike -InconclusiveMatchError which allows trying other conditions. - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`reason` | `UnresolvedFlagReason` | Why the flag needs server-side evaluation. - -#### `posthog.feature_flags.RequiresServerEvaluation.reason` - -```python -reason: UnresolvedFlagReason = UnresolvedFlagReason.UNSUPPORTED_DEFINITION -``` - -### `Span` - -A handle to a span. - -Every method is safe to call on any handle, including after `end()` and -on the inert handles returned when tracing is off, so calling code never -branches on whether tracing is running. Entering a handle (`with span:`) -makes it the active span for the block and ends it on exit, recording an -`Exception` raised inside it. A `BaseException` that is not an -`Exception` (`GeneratorExit`, `CancelledError`, `KeyboardInterrupt`) -still ends the span but is not recorded as a failure. - -**Functions:** - -Name | Description ----- | ----------- -`add_event` | Add a timestamped event to the span. Ignored after `end()`. -`end` | End the span and queue it for export. Idempotent. -`record_exception` | Record an exception as an `exception` event and mark the span `error`. -`set_attribute` | Set one attribute on the span; last write wins. Ignored after `end()`. -`set_attributes` | Set several attributes at once; last write wins per key. Ignored after `end()`. -`set_status` | Set the span status to `"ok"` or `"error"`. Ignored after `end()`. -`traceparent` | The W3C `traceparent` header value to propagate, or `None`. -`tracestate` | The W3C `tracestate` header value to propagate, or `None`. -`update_name` | Rename the span, for a name only known after it started. Ignored after `end()`. - -#### `posthog.tracing.span.Span.add_event` - -```python -add_event(name: str, attributes: Optional[Mapping[str, Any]] = None, timestamp: Union[datetime, float, None] = None) -> Span -``` - -Add a timestamped event to the span. Ignored after `end()`. - -`timestamp` defaults to now; it accepts a `datetime`, or seconds -since the epoch as a `float` or `int`. Returns the span, so calls -chain. - -**Examples:** - -```python -span.add_event("cache.miss", {"key": "user:42"}) -``` - -#### `posthog.tracing.span.Span.end` - -```python -end(end_time: Union[datetime, float, None] = None) -> None -``` - -End the span and queue it for export. Idempotent. - -`end_time` defaults to now; it accepts a `datetime`, or seconds -since the epoch as a `float` or `int`, and is never earlier than -the start. Later calls, and later mutations, no-op. `with span:` -ends the span on exit, so call this only for spans started manually. - -**Examples:** - -```python -span = posthog.start_span("job") -try: - run_job() -finally: - span.end() -``` - -#### `posthog.tracing.span.Span.record_exception` - -```python -record_exception(exception: BaseException) -> Span -``` - -Record an exception as an `exception` event and mark the span `error`. - -Ignored after `end()`. The event carries `exception.type`, -`exception.message` and, for a raised exception, -`exception.stacktrace` (its last `max_attribute_value_length` -characters, so a chained traceback keeps the outermost exception and -may lose its cause). Returns the span, so calls chain. Inside -`with span:` a raised `Exception` is recorded automatically, so -this is for exceptions that are caught and handled. - -**Examples:** - -```python -try: - charge(card) -except PaymentError as e: - span.record_exception(e) -``` - -#### `posthog.tracing.span.Span.set_attribute` - -```python -set_attribute(key: str, value: Any) -> Span -``` - -Set one attribute on the span; last write wins. Ignored after `end()`. - -Returns the span, so calls chain. Prefer primitive values: strings, -booleans, integers and floats keep their type. Lists and mappings are -sent as OTLP arrays and maps, but PostHog stores them as serialized -strings. `None` removes the key; anything else is stringified. - -**Examples:** - -```python -span.set_attribute("http.status_code", 200).set_attribute("cache.hit", True) -``` - -#### `posthog.tracing.span.Span.set_attributes` - -```python -set_attributes(attributes: Mapping[str, Any]) -> Span -``` - -Set several attributes at once; last write wins per key. Ignored after `end()`. - -Returns the span, so calls chain. Same encoding as `set_attribute()`. - -**Examples:** - -```python -span.set_attributes({"db.system": "postgresql", "db.rows": 12}) -``` - -#### `posthog.tracing.span.Span.set_status` - -```python -set_status(code: str, message: Optional[str] = None) -> Span -``` - -Set the span status to `"ok"` or `"error"`. Ignored after `end()`. - -Unset by default. Any other `code` is ignored. `ok` is final for the -scoped form: an `Exception` raised inside `with span:` does not override -it. Returns the span, so calls chain. - -**Examples:** - -```python -span.set_status("error", "upstream timed out") -``` - -#### `posthog.tracing.span.Span.traceparent` - -```python -traceparent() -> Optional[str] -``` - -The W3C `traceparent` header value to propagate, or `None`. - -#### `posthog.tracing.span.Span.tracestate` - -```python -tracestate() -> Optional[str] -``` - -The W3C `tracestate` header value to propagate, or `None`. - -#### `posthog.tracing.span.Span.update_name` - -```python -update_name(name: str) -> Span -``` - -Rename the span, for a name only known after it started. Ignored after `end()`. - -Returns the span, so calls chain. - -**Examples:** - -```python -span = posthog.start_span("http.request") -span.update_name(f"{request.method} {route.pattern}") -``` - -### `Emails` - -```python -Emails(secret_key: Optional[str], *, host: Optional[str] = None, timeout: float = 15, max_retries: int = 2, disabled: bool = False, session: Optional[requests.Session] = None) -> None -``` - -Bases: `_EmailsBase` - -Sends transactional email from PostHog email templates. - -`host` is your PostHog app host. PostHog Cloud ingestion hosts map to the -app host, so `us.i.posthog.com` sends to `us.posthog.com`. Any other -host is used as given. - -Each send retries network errors, rate limits and an unavailable service up -to `max_retries` times, with the same idempotency key. Set -`max_retries=0` when a job runner already retries the send. `timeout` -is in seconds, per request. Pass `session` to send through your own -`requests.Session`, for example a test double. - -**Functions:** - -Name | Description ----- | ----------- -`close` | Close the HTTP connections this client opened. A `session` you passed in stays open. -`get` | Read what happened to an email that `send` queued. -`send` | Queue one email from a transactional email template. - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`disabled` | | -`host` | | -`max_retries` | | -`secret_key` | | -`timeout` | | - -#### `posthog.emails.Emails.close` - -```python -close() -> None -``` - -Close the HTTP connections this client opened. A `session` you passed in stays open. - -#### `posthog.emails.Emails.disabled` - -```python -disabled = disabled -``` - -#### `posthog.emails.Emails.get` - -```python -get(message_id: Union[str, UUID]) -> EmailStatus -``` - -Read what happened to an email that `send` queued. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`message_id` | `Union[str, UUID]` | The `id` of the `SendEmailResult` that `send` returned. | *required* - -**Returns:** - -Type | Description ----- | ----------- -`EmailStatus` | The email's latest status, for 30 days after the send. - -**Raises:** - -Type | Description ----- | ----------- -`EmailError` | PostHog did not answer with a status. `code` is `not_found` for an unknown ID, an email older than 30 days, or for a few seconds after the send. -`ValueError` | `message_id` is not a message ID. - -**Examples:** - -```python -result = emails.send(to="user@example.com", template_id="...") -status = emails.get(result.id) -if status.status == "bounced": - print(status.reason) -``` - -#### `posthog.emails.Emails.host` - -```python -host = determine_app_host(host) -``` - -#### `posthog.emails.Emails.max_retries` - -```python -max_retries = max_retries -``` - -#### `posthog.emails.Emails.secret_key` - -```python -secret_key = secret_key.strip() if isinstance(secret_key, str) else None -``` - -#### `posthog.emails.Emails.send` - -```python -send(*, to: str, template_id: Union[str, UUID], template_data: Optional[Mapping[str, Any]] = None, distinct_id: Optional[str] = None, idempotency_key: Optional[str] = None, max_retries: Optional[int] = None) -> SendEmailResult -``` - -Queue one email from a transactional email template. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`to` | `str` | One recipient email address. | *required* -`template_id` | `Union[str, UUID]` | The UUID of a template made transactional in PostHog. | *required* -`template_data` | `Optional[Mapping[str, Any]]` | Values for the template's `{{ variables.* }}` placeholders. Values are encoded the way capture encodes properties. | `None` -`distinct_id` | `Optional[str]` | The recipient's distinct ID in your product. The email's events go to this person. Without it, they go to a person whose distinct ID is the email address. | `None` -`idempotency_key` | `Optional[str]` | 1 to 255 visible ASCII characters. A send with a key that PostHog has already accepted queues nothing and returns the first send's result. Without it, the client makes a key that covers its own retries. Pass a key derived from what the email is for, such as `welcome:user_123`, to cover retries outside this call, too. | `None` -`max_retries` | `Optional[int]` | Overrides the client's maximum number of retries for this send. `0` turns retries off. | `None` - -**Returns:** - -Type | Description ----- | ----------- -`SendEmailResult` | The queued email. A result means PostHog queued the email, not that -`SendEmailResult` | it was delivered. - -**Raises:** - -Type | Description ----- | ----------- -`EmailError` | PostHog did not queue the email, or the client could not tell whether it did. -`(TypeError, ValueError)` | An argument has the wrong type or format. - -**Examples:** - -```python -emails.send( - to="user@example.com", - template_id="0190f6d2-7c1e-7b3a-9f5e-1c2d3e4f5a6b", - template_data={"name": "Ada"}, - distinct_id="user_123", - idempotency_key="welcome:user_123", -) -``` - -#### `posthog.emails.Emails.timeout` - -```python -timeout = timeout -``` - -### `EmailError` - -```python -EmailError(*, name: str, code: str, message: str, status_code: Optional[int] = None, why: Optional[str] = None, fix: Optional[str] = None, docs_url: Optional[str] = None, field: Optional[str] = None, retry_after: Optional[float] = None) -> None -``` - -Bases: `Exception` - -PostHog did not queue the email, or the client could not tell whether it did. - -Branch on `code`, PostHog's stable error code, or on `name`, a coarse -category of the failure. The message never contains the secret key or the -request body. - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`name` | | A coarse category of the failure. -`code` | | PostHog's stable error code. -`message` | | What went wrong. It never contains the secret key or the request body. -`status_code` | | The HTTP status of the response, or `None` when none arrived. -`why` | | PostHog's explanation of the failure, when it gave one. -`fix` | | What to change to fix it, when PostHog said. -`docs_url` | | A docs page about the error, when PostHog gave one. -`field` | | The request field the error is about, when there is one. -`retry_after` | | Seconds to wait before retrying, when PostHog said. - -#### `posthog.emails.EmailError.code` - -```python -code = code -``` - -#### `posthog.emails.EmailError.docs_url` - -```python -docs_url = docs_url -``` - -#### `posthog.emails.EmailError.field` - -```python -field = field -``` - -#### `posthog.emails.EmailError.fix` - -```python -fix = fix -``` - -#### `posthog.emails.EmailError.message` - -```python -message = message -``` - -#### `posthog.emails.EmailError.name` - -```python -name = name -``` - -#### `posthog.emails.EmailError.retry_after` - -```python -retry_after = retry_after -``` - -#### `posthog.emails.EmailError.status_code` - -```python -status_code = status_code -``` - -#### `posthog.emails.EmailError.why` - -```python -why = why -``` - -### `SendEmailResult` - -```python -SendEmailResult(id: str, replayed: bool = False, warnings: tuple[EmailWarning, ...] = ()) -> None -``` - -The queued email. `id` is its message ID. - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`id` | `str` | The email's message ID. -`replayed` | `bool` | `True` when the response says PostHog replayed an earlier send for the same `idempotency_key` instead of sending again. -`warnings` | `tuple[EmailWarning, ...]` | Warnings PostHog returned about the request. - -#### `posthog.emails.SendEmailResult.id` - -```python -id: str -``` - -#### `posthog.emails.SendEmailResult.replayed` - -```python -replayed: bool = False -``` - -#### `posthog.emails.SendEmailResult.warnings` - -```python -warnings: tuple[EmailWarning, ...] = () -``` diff --git a/references/posthog-python-8.md b/references/posthog-python-8.md deleted file mode 100644 index 325838400..000000000 --- a/references/posthog-python-8.md +++ /dev/null @@ -1,4363 +0,0 @@ ---- -title: "PostHog Python SDK 8.x (experimental)" -sidebar: Docs -showTitle: true -contentMaxWidthClass: max-w-full -platformLogo: python -tableOfContents: - - value: "Getting started" - url: getting-started - depth: 0 - - value: "Traces" - url: traces - depth: 0 - - value: "Alias" - url: alias - depth: 0 - - value: "Capture" - url: capture - depth: 0 - - value: "Capture AI" - url: capture-ai - depth: 0 - - value: "Capture exception" - url: capture-exception - depth: 0 - - value: "Emails" - url: emails - depth: 0 - - value: "Flush" - url: flush - depth: 0 - - value: "Get feature flag" - url: get-feature-flag - depth: 0 - - value: "Get feature flag payload" - url: get-feature-flag-payload - depth: 0 - - value: "Get feature flag result" - url: get-feature-flag-result - depth: 0 - - value: "Get feature flags" - url: get-feature-flags - depth: 0 - - value: "Get feature flags and payloads" - url: get-feature-flags-and-payloads - depth: 0 - - value: "Evaluate flags" - url: evaluate-flags - depth: 0 - - value: "Group identify" - url: group-identify - depth: 0 - - value: "Identify" - url: identify - depth: 0 - - value: "Is feature enabled" - url: is-feature-enabled - depth: 0 - - value: "Setup" - url: setup - depth: 0 - - value: "Shutdown" - url: shutdown - depth: 0 - - value: "Flag definition loader" - url: flag-definition-loader - depth: 0 - - value: "SDK utils" - url: sdk-utils - depth: 0 - - value: "AsyncClient" - url: asyncclient - depth: 0 - - value: "Module functions" - url: module-functions - depth: 0 - - value: "Types" - url: types - depth: 0 ---- - -## Getting started - - -The Python SDK captures events, evaluates feature flags and reports errors from Python 3.10 or later. - -### Install - -```bash -pip install posthog -``` - -### Initialize - -Create one client for your app with your project token and host. Your project token and host are in your [project settings](https://app.posthog.com/project/settings). Keep the token in an environment variable rather than in code. - -```python -from posthog import Posthog - -posthog = Posthog("", host="") -``` - -### Capture an event - -```python -posthog.capture("user_signed_up", distinct_id="user_123", properties={"plan": "free"}) -``` - -`capture` queues the event and returns right away. A background thread sends it. - -### Shut down - -Call `shutdown()` before your process exits, so queued events are sent. - -```python -posthog.shutdown() -``` - -In an asyncio app, use `AsyncPosthog` as an async context manager instead. See [AsyncClient](#asyncclient). - -## Traces - -### `Client.start_span` - -```python -start_span(name: str, *, kind: Optional[str] = None, attributes: Optional[Mapping[str, Any]] = None, parent: Union[Span, str, None] = None, tracestate: Optional[str] = None, start_time: Union[datetime, float, None] = None) -> Span -``` - -Start a span for distributed tracing. Alpha. - -Returns a span handle. Use it as a context manager to make it the active -span for the block and end it on exit (recording a raised exception on -the way out); or call `end()` yourself for a span that cannot wrap a -block. Spans started inside the block nest under it automatically. -Always returns a usable handle, even when tracing is off, so calling -code never branches. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`name` | `str` | A low-cardinality operation name, e.g. `GET /users/:id`. Variable values belong in attributes, not the name. | *required* -`kind` | `Optional[str]` | `internal` (default), `server`, `client`, `producer` or `consumer`. | `None` -`attributes` | `Optional[Mapping[str, Any]]` | Initial attributes. | `None` -`parent` | `Union[Span, str, None]` | A span handle, or an inbound W3C `traceparent` header value to continue a remote trace. Defaults to the active span. A forked child starts with no active span; pass the parent span to continue a trace across a fork. | `None` -`tracestate` | `Optional[str]` | The inbound `tracestate` header accompanying a `traceparent` string `parent`; preserved and propagated. | `None` -`start_time` | `Union[datetime, float, None]` | A `datetime` or epoch seconds, to backdate the span. | `None` - -**Examples:** - -```python -posthog = Posthog("", traces={"service_name": "checkout-api"}) - -with posthog.start_span("POST /checkout", parent=request.headers.get("traceparent")) as span: - span.set_attribute("plan", user.plan) - with posthog.start_span("db.query", kind="client"): - ... - outgoing_headers = {"traceparent": span.traceparent()} -``` - -### `Client.get_active_span` - -```python -get_active_span() -> Optional[Span] -``` - -The span that is active in the current context, or `None`. Alpha. - -Only entering a span (`with posthog.start_span(...) as span:`) makes -it active; a span started manually is not. Use it to propagate the -trace to the next service: `span.traceparent()` is the header value. - -**Examples:** - -```python -span = posthog.get_active_span() -if span is not None: - headers["traceparent"] = span.traceparent() -``` - -## Alias - -### `Client.alias` - -```python -alias(previous_id: ID_TYPES, distinct_id: Optional[str], timestamp: Optional[Union[datetime, str]] = None, uuid: Optional[str] = None, disable_geoip: Optional[bool] = None, options: Optional[Dict[str, Any]] = None) -> Optional[str] -``` - -Create an alias between two distinct IDs. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`previous_id` | `ID_TYPES` | The previous distinct ID. Required - the call is dropped with a warning if it is missing or empty. | *required* -`distinct_id` | `Optional[str]` | The new distinct ID to alias to. Required - the call is dropped with a warning if it is missing or empty. The context distinct ID is never used here. | *required* -`timestamp` | `Optional[Union[datetime, str]]` | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | `None` -`uuid` | `Optional[str]` | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | `None` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this event. | `None` -`options` | `Optional[Dict[str, Any]]` | Capture options for this event, sent as given. | `None` - -**Examples:** - -```python -posthog.alias(previous_id='distinct_id', distinct_id='alias_id') -``` - -Note: This method will not raise exceptions. Errors are logged. - -## Capture - -### `Client.capture` - -```python -capture(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] -``` - -Captures an event manually. [Learn about capture best practices](https://posthog.com/docs/product-analytics/capture-events) - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`event` | `str` | The event name to capture. | *required* -`distinct_id` | | The distinct ID of the user. | -`properties` | | A dictionary of properties to include with the event. | -`timestamp` | | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | -`uuid` | | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | -`groups` | | A dictionary of group information. It merges into a `$groups` property and wins key by key. | -`flags` | | A FeatureFlagEvaluations snapshot from evaluate_flags(). The exact values from the snapshot are attached with no extra /flags request. | -`send_feature_flags` | | Deprecated. Prefer flags=... from evaluate_flags(). When truthy, evaluates flags during capture and attaches them to the event. | -`disable_geoip` | | Whether to disable GeoIP for this event. It wins over a `$geoip_disable` in context tags or `super_properties`. A `$geoip_disable` property in this call's `properties` wins over it. | -`options` | | Capture options for this event, such as `{"process_person_profile": False}`. Sent as given, for PostHog to validate. They override context options and `super_options`, which fill in before `before_send` runs. An option set at any layer wins over its legacy `$` property, such as `$process_person_profile`, set at any layer. A `None` option counts as unset. | - -**Examples:** - -```python -# Anonymous event -posthog.capture('some-anon-event') -``` -```python -# Context usage -from posthog import identify_context, new_context -with new_context(): - identify_context('distinct_id_of_the_user') - posthog.capture('user_signed_up') - posthog.capture('user_logged_in') - posthog.capture('some-custom-action', distinct_id='distinct_id_of_the_user') -``` -```python -# Set event properties -posthog.capture( - "user_signed_up", - distinct_id="distinct_id_of_the_user", - properties={ - "login_type": "email", - "is_free_trial": "true" - } -) -``` -```python -# Page view event -posthog.capture('$pageview', distinct_id="distinct_id_of_the_user", properties={'$current_url': 'https://example.com'}) -``` -```python -# Counter example (incorrect): capture keeps the first event for each uuid in a request and drops the later copies -event_id = str(uuid.uuid4()) -posthog.capture("order_placed", distinct_id="user_123", uuid=event_id) -posthog.capture("order_paid", distinct_id="user_123", uuid=event_id) # dropped as duplicate_event_uuid -``` - -## Capture AI - -### `Client.capture_ai` - -```python -capture_ai(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] -``` - -Capture an AI event on the dedicated AI capture endpoint. - -Beta: the signature is stable; operational limits (per-event size -cap, batching, endpoint) may change without notice. - -Takes the same arguments and returns the same value as `capture()`: -the event UUID, or None when the event was not admitted (disabled -client, or dropped by `before_send`). The event is queued on an -isolated AI lane with its own consumer pool and a higher per-event -size cap, posting to the dedicated AI ingestion endpoint. The payload -is sent as given — no redaction or truncation is applied here. - -## Capture exception - -### `Client.capture_exception` - -```python -capture_exception(exception: Optional[ExceptionArg], *, level: Optional[str] = None, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] -``` - -Capture an exception for error tracking. - -When OpenTelemetry is installed and a valid span is active, its trace and -span IDs are added as `$trace_id` and `$span_id` event properties, -unless the event, a context tag or `super_properties` sets either one. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`exception` | `Optional[ExceptionArg]` | The exception to capture. | *required* -`level` | `Optional[str]` | The `$exception_level`, such as `"warning"` or `"fatal"`. Defaults to `"error"`. An unknown value counts as unset. | `None` -`distinct_id` | | The distinct ID of the user. | -`properties` | | A dictionary of additional properties. Reserved exception properties, such as `$exception_list` and `$exception_level`, are ignored. | -`flags` | | A `FeatureFlagEvaluations` snapshot from `evaluate_flags()`. Attaches those exact flag values to the captured `$exception` event. | -`send_feature_flags` | | Deprecated. Pass `flags` from `evaluate_flags()` instead. | -`disable_geoip` | | Whether to disable GeoIP for this event. | - -**Examples:** - -```python -try: - # Some code that might fail - pass -except Exception as e: - posthog.capture_exception(e, 'user_distinct_id', properties=additional_properties) -``` - -## Emails - -### `Client.emails` - -```python -emails: Emails -``` - -Send transactional email from PostHog email templates, and read the -status of each email sent with `get`. - -Needs `secret_key`, a project secret API key with the `hog_flow:write` -scope. Sends go to `app_host`, or to the PostHog Cloud app host of -`host`. `email_request_timeout_seconds` and `email_request_max_retries` -configure each send. A disabled or shut-down client sends nothing. See -`posthog.emails` for setup. - -**Examples:** - -```python -client.emails.send( - to="user@example.com", - template_id="0190f6d2-7c1e-7b3a-9f5e-1c2d3e4f5a6b", - template_data={"name": "Ada"}, - distinct_id="user_123", -) -``` - -## Flush - -### `Client.flush` - -```python -flush(timeout_seconds: Optional[float] = 10) -> None -``` - -Attempt immediate delivery of queued events, ended spans and autocaptured metrics, keeping the client reusable. - -For a client shared across requests or serverless invocations (including -the module-level client), call `flush()` before a short-lived invocation -returns. Use `shutdown()` instead only for final process cleanup or when -disposing of a client that will not be reused. Flush does not stop the -background workers or feature flag poller, and does not end open spans. - -Returning does not guarantee server receipt: pending work can remain after -a timeout or span export failure, and failed events may be dropped by the -consumers. Calls made directly from SDK callbacks such as `on_error` are -deferred rather than blocking the worker that invoked the callback. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`timeout_seconds` | `Optional[float]` | Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass `None` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently. The span exporter waits up to this many seconds for an in-flight flush, then uses a fresh drain budget once it acquires the lock. If the lock wait times out, the in-flight flush is left to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. Each span request is bounded by the client's `timeout` in seconds, and the last request is not cut short. This is therefore not a strict wall-clock limit for the whole call. With `None`, a retriable span failure is left for a later flush rather than retried within this call. | `10` - -**Examples:** - -```python -posthog.capture('event_name', distinct_id='user_id') -# Before a serverless invocation returns; keep the shared client open. -posthog.flush(timeout_seconds=10) -``` - -## Get feature flag - -### `Client.get_feature_flag` - -```python -get_feature_flag(key: str, distinct_id: ID_TYPES, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[FlagValue] -``` - -Get multivariate feature flag value for a user. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The feature flag key. | *required* -`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` -`only_evaluate_locally` | `bool` | Whether to only evaluate locally. | `False` -`send_feature_flag_events` | `bool` | Whether to send feature flag events. | `True` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` -`device_id` | `Optional[str]` | The device ID for this request. | `None` - -**Examples:** - -```python -enabled_variant = posthog.get_feature_flag('flag-key', 'distinct_id_of_your_user') -if enabled_variant == 'variant-key': # replace 'variant-key' with the key of your variant - # Do something differently for this user - # Optional: fetch the payload - matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user') -``` - -## Get feature flag payload - -### `Client.get_feature_flag_payload` - -```python -get_feature_flag_payload(key: str, distinct_id: ID_TYPES, *, match_value: Optional[FlagValue] = None, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = False, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[object] -``` - -Get the payload for a feature flag. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The feature flag key. | *required* -`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* -`match_value` | `Optional[FlagValue]` | The specific flag value to get payload for. | `None` -`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` -`only_evaluate_locally` | `bool` | Whether to only evaluate locally. | `False` -`send_feature_flag_events` | `bool` | Deprecated. Use get_feature_flag() instead if you need events. | `False` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` -`device_id` | `Optional[str]` | The device ID for this request. | `None` - -**Returns:** - -Type | Description ----- | ----------- -`Optional[object]` | The payload associated with the matched feature flag value, or None. -`Optional[object]` | This method returns the payload only, not the FeatureFlagResult wrapper -`Optional[object]` | used internally to compute it. - -**Examples:** - -```python -is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user') - -if is_my_flag_enabled: - # Do something differently for this user - # Optional: fetch the payload - matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user') -``` - -## Get feature flag result - -### `Client.get_feature_flag_result` - -```python -get_feature_flag_result(key: str, distinct_id: ID_TYPES, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[FeatureFlagResult] -``` - -Get a FeatureFlagResult object which contains the flag result and payload for a key by evaluating locally or remotely -depending on whether local evaluation is enabled and the flag can be locally evaluated. -This also captures the `$feature_flag_called` event unless `send_feature_flag_events` is `False`. - -**Examples:** - -```python -flag_result = posthog.get_feature_flag_result('flag-key', 'distinct_id_of_your_user') -if flag_result and flag_result.get_value() == 'variant-key': - # Do something differently for this user - # Optional: fetch the payload - matched_flag_payload = flag_result.payload -``` - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The feature flag key. | *required* -`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` -`only_evaluate_locally` | `bool` | Whether to only evaluate locally. | `False` -`send_feature_flag_events` | `bool` | Whether to send feature flag events. | `True` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` -`device_id` | `Optional[str]` | The device ID for this request. | `None` - -**Returns:** - -Type | Description ----- | ----------- -`Optional[FeatureFlagResult]` | Optional[FeatureFlagResult]: The feature flag result or None if disabled/not found. - -## Get feature flags - -### `Client.get_feature_variants` - -```python -get_feature_variants(distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, disable_geoip: Optional[bool] = None, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None) -> dict[str, Union[bool, str]] -``` - -Get feature flag variants for a user. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` -`flag_keys_to_evaluate` | `Optional[list[str]]` | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | `None` -`device_id` | `Optional[str]` | The device ID for this request. | `None` - -### `Client.get_all_flags` - -```python -get_all_flags(distinct_id: ID_TYPES, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None) -> Optional[dict[str, Union[bool, str]]] -``` - -Get all feature flags for a user. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` -`only_evaluate_locally` | `bool` | Whether to only evaluate locally. | `False` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` -`flag_keys_to_evaluate` | `Optional[list[str]]` | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | `None` -`device_id` | `Optional[str]` | The device ID for this request. | `None` - -**Examples:** - -```python -posthog.get_all_flags('distinct_id_of_your_user') -``` - -## Get feature flags and payloads - -### `Client.get_feature_flags_and_payloads` - -```python -get_feature_flags_and_payloads(distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, disable_geoip: Optional[bool] = None, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None) -> FlagsAndPayloads -``` - -Get feature flags and payloads for a user. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` -`flag_keys_to_evaluate` | `Optional[list[str]]` | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | `None` -`device_id` | `Optional[str]` | The device ID for this request. | `None` - -**Examples:** - -```python -result = posthog.get_feature_flags_and_payloads('') -``` - -### `Client.get_all_flags_and_payloads` - -```python -get_all_flags_and_payloads(distinct_id: ID_TYPES, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None) -> FlagsAndPayloads -``` - -Get all feature flags and their payloads for a user. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` -`only_evaluate_locally` | `bool` | Whether to only evaluate locally. | `False` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` -`flag_keys_to_evaluate` | `Optional[list[str]]` | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | `None` -`device_id` | `Optional[str]` | The device ID for this request. | `None` - -**Examples:** - -```python -posthog.get_all_flags_and_payloads('distinct_id_of_your_user') -``` - -## Evaluate flags - -### `Client.evaluate_flags` - -```python -evaluate_flags(distinct_id: Optional[ID_TYPES] = None, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, flag_keys: Optional[List[str]] = None, device_id: Optional[str] = None) -> FeatureFlagEvaluations -``` - -Evaluate all feature flags for a user in a single call and return a -`FeatureFlagEvaluations` snapshot. Branch on `.is_enabled()` / -`.get_flag()` and pass the same snapshot to `capture` via the -`flags` option so events carry the exact flag values the code branched on. - -Prefer this over repeated `get_feature_flag()` calls and over -`capture(send_feature_flags=True)` — it consolidates flag evaluation into -a single `/flags` request per incoming request. - -Local evaluation is transparent: when the poller resolves a flag, the -snapshot's `$feature_flag_called` events are tagged `locally_evaluated=True` -and reason `"Evaluated locally"`. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | `Optional[ID_TYPES]` | The user's distinct ID. If `None`, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot. | `None` -`groups` | `Optional[Mapping[str, Union[str, int]]]` | Mapping of group type to group key. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | Person properties to use for evaluation. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties keyed by group type. | `None` -`only_evaluate_locally` | `bool` | If True, never fall back to remote evaluation — flags that can't be evaluated locally are omitted from the snapshot and listed with a reason in its `unresolved_flags`. | `False` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup. | `None` -`flag_keys` | `Optional[List[str]]` | Optional list that scopes local evaluation, the underlying `/flags` request, and the returned snapshot. When omitted or `None`, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per `evaluate_flags` call unless `only_evaluate_locally` is True. If the server also does not know the key, it is omitted from the snapshot. | `None` -`device_id` | `Optional[str]` | Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes. | `None` - -**Returns:** - -Type | Description ----- | ----------- -`FeatureFlagEvaluations` | A `FeatureFlagEvaluations` snapshot. - -**Examples:** - -```python -flags = posthog.evaluate_flags( - "user_123", - person_properties={"plan": "enterprise"}, -) -if flags.is_enabled("new-dashboard"): - render_new_dashboard() -posthog.capture("page_viewed", distinct_id="user_123", flags=flags) -``` -```python -# Counter example (incorrect): the event can carry different flag values than the code branched on -if posthog.get_feature_flag("new-dashboard", "user_123"): - render_new_dashboard() -posthog.capture("page_viewed", distinct_id="user_123", send_feature_flags=True) # evaluates flags again -``` - -## Group identify - -### `Client.group_identify` - -```python -group_identify(group_type: str, group_key: str, properties: Optional[Dict[str, Any]] = None, timestamp: Optional[Union[datetime, str]] = None, uuid: Optional[Union[str, UUID]] = None, disable_geoip: Optional[bool] = None, distinct_id: Optional[ID_TYPES] = None, options: Optional[Dict[str, Any]] = None) -> Optional[str] -``` - -Identify a group and set its properties. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`group_type` | `str` | The type of group (e.g., 'company', 'team'). Required - the call is dropped with a warning if it is missing or empty. | *required* -`group_key` | `str` | The unique identifier for the group. Required - the call is dropped with a warning if it is missing or empty. | *required* -`properties` | `Optional[Dict[str, Any]]` | A dictionary of properties to set on the group. | `None` -`timestamp` | `Optional[Union[datetime, str]]` | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | `None` -`uuid` | `Optional[Union[str, UUID]]` | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | `None` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this event. | `None` -`distinct_id` | `Optional[ID_TYPES]` | The distinct ID of the user performing the action. Falls back to the context distinct ID, then to `$_`. | `None` -`options` | `Optional[Dict[str, Any]]` | Capture options for this event, sent as given. | `None` - -**Examples:** - -```python -posthog.group_identify('company', 'company_id_in_your_db', { - 'name': 'Awesome Inc.', - 'employees': 11 -}) -``` - -Note: This method will not raise exceptions. Errors are logged. - -## Identify - -### `Client.set` - -```python -set(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str] -``` - -Set properties on a person profile. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | | The distinct ID of the user. | -`properties` | | A dictionary of properties to set. | -`timestamp` | | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | -`uuid` | | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | -`disable_geoip` | | Whether to disable GeoIP for this event. | - -**Examples:** - -```python -# Set with distinct id -posthog.set(distinct_id='user123', properties={'name': 'Max Hedgehog'}) -``` - -Note: This method will not raise exceptions. Errors are logged. - -### `Client.set_once` - -```python -set_once(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str] -``` - -Set properties on a person profile only if they haven't been set before. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | | The distinct ID of the user. | -`properties` | | A dictionary of properties to set once. | -`timestamp` | | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | -`uuid` | | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | -`disable_geoip` | | Whether to disable GeoIP for this event. | - -**Examples:** - -```python -posthog.set_once(distinct_id='user123', properties={'initial_signup_date': '2024-01-01'}) -``` - -Note: This method will not raise exceptions. Errors are logged. - -## Is feature enabled - -### `Client.feature_enabled` - -```python -feature_enabled(key: str, distinct_id: ID_TYPES, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[bool] -``` - -Check if a feature flag is enabled for a user. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The feature flag key. | *required* -`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` -`only_evaluate_locally` | `bool` | Whether to only evaluate locally. | `False` -`send_feature_flag_events` | `bool` | Whether to send feature flag events. | `True` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` -`device_id` | `Optional[str]` | The device ID for this request. | `None` - -**Examples:** - -```python -is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user') -if is_my_flag_enabled: - # Do something differently for this user - # Optional: fetch the payload - matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user') -``` - -## Setup - -### `Client(...)` - -```python -__init__(project_api_key: str, host: str = None, *, debug: str = False, max_queue_size: str = 10000, send: str = True, on_error: str = None, flush_at: str = 100, flush_interval: str = 5.0, max_retries: str = 3, sync_mode: str = False, timeout: float = 15, thread: float = 1, poll_interval: float = 30, personal_api_key: float = None, disabled: float = False, disable_geoip: float = True, is_server: float = True, historical_migration: float = False, feature_flags_request_timeout_seconds: float = 3, feature_flags_request_max_retries: float = 1, super_properties: float = None, super_options: float = None, enable_exception_autocapture: float = False, log_captured_exceptions: float = False, project_root: float = None, privacy_mode: float = False, before_send: float = None, flag_fallback_cache_url: float = None, enable_local_evaluation: float = True, flag_definition_cache_provider: Optional[FlagDefinitionCacheProvider] = None, capture_exception_code_variables: Optional[FlagDefinitionCacheProvider] = False, code_variables_mask_patterns: Optional[FlagDefinitionCacheProvider] = None, code_variables_ignore_patterns: Optional[FlagDefinitionCacheProvider] = None, code_variables_mask_url_credentials: Optional[FlagDefinitionCacheProvider] = None, code_variables_detect_secrets: Optional[FlagDefinitionCacheProvider] = None, in_app_modules: list[str] | None = None, enable_exception_autocapture_rate_limiting: list[str] | None = False, exception_autocapture_bucket_size: list[str] | None = ExceptionCapture.DEFAULT_BUCKET_SIZE, exception_autocapture_refill_rate: list[str] | None = ExceptionCapture.DEFAULT_REFILL_RATE, exception_autocapture_refill_interval_seconds: list[str] | None = ExceptionCapture.DEFAULT_REFILL_INTERVAL_SECONDS, capture_compression: Optional[Union[CaptureCompression, str]] = None, capture_ai_compression: Optional[Union[CaptureCompression, str]] = None, capture_ai_max_queue_size: int = 1000, capture_ai_timeout: float = 30, capture_ai_max_event_bytes: int = AI_MAX_MSG_SIZE, secret_key: int = None, metrics: Optional[dict] = None, enable_full_ai_capture: Optional[dict] = False, capture_trace_context: Optional[dict] = False, _use_ai_lane: Optional[dict] = False, _enable_multimodal_capture: Optional[dict] = False, traces: Optional[dict] = None, app_host: Optional[str] = None, email_request_timeout_seconds: float = 15, email_request_max_retries: int = 2) -``` - -Initialize a new PostHog client instance. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`project_api_key` | `str` | PostHog project API key/token. | *required* -`host` | | PostHog host. Defaults to the US ingestion endpoint when not set. App hosts such as `https://us.posthog.com` are mapped to the corresponding ingestion host. | `None` -`debug` | | Enable verbose SDK logging and re-raise errors from public API methods. | `False` -`max_queue_size` | | Maximum number of analytics events buffered before upload. AI events use `capture_ai_max_queue_size`. | `10000` -`send` | | If False, queueing succeeds but events are not sent. | `True` -`on_error` | | Optional callback `(error, batch)` invoked when an upload fails: by background consumers, or on the calling thread in `sync_mode`. Capture failures arrive as `CaptureError`. Without it, each failed batch logs one aggregate line. A capture that fails inside the callback logs that line instead of calling it again. Keep it short and non-blocking. Calling lifecycle methods directly is safe and deferred, but do not start another thread or task that calls `flush()`, `join()`, or `shutdown()` and then wait for it from the callback. | `None` -`flush_at` | | Number of queued events that triggers a batch upload. | `100` -`flush_interval` | | Maximum seconds a background consumer waits before flushing a partial batch. | `5.0` -`max_retries` | | Number of upload retries. Values below 0 are treated as 0. | `3` -`sync_mode` | | If True, send each event synchronously instead of using background worker threads. This blocks the calling thread; in asyncio applications such as FastAPI, use `AsyncPosthog` instead. | `False` -`timeout` | `float` | HTTP request timeout in seconds for event uploads. | `15` -`thread` | | Number of background consumer threads. | `1` -`poll_interval` | | Seconds between local feature flag definition refreshes. | `30` -`secret_key` | | A Personal API Key or Project Secret API Key, used to authenticate local feature flag evaluation, remote config payloads, and decrypted flag payloads. Example::
posthog.Client(project_api_key, secret_key="phx_...") | `None` -`personal_api_key` | | Deprecated alias for `secret_key`. Still honored for backwards compatibility; prefer `secret_key`, which also accepts a Project Secret API Key. | `None` -`disabled` | | If True, disable captures and API requests. Useful in tests. | `False` -`disable_geoip` | | Whether to disable server-side GeoIP enrichment. Defaults to True. Events get `$geoip_disable` only when the event, context tags and `super_properties` leave it unset. | `True` -`is_server` | | Whether events are emitted from a server-side runtime. Defaults to True; set to False when using the SDK as a client/CLI so the device OS is attributed to the person normally. Events get `$is_server` only when the event, context tags and `super_properties` leave it unset. | `True` -`historical_migration` | | Mark events as historical migration imports. | `False` -`feature_flags_request_timeout_seconds` | `float` | Timeout in seconds for feature flag and remote config requests. | `3` -`feature_flags_request_max_retries` | | Number of retries for feature flag requests after network, transport, or timeout failures. Defaults to 1. Set to 0 to disable retries. | `1` -`app_host` | `Optional[str]` | Your PostHog app host, such as `https://posthog.example.com`. `emails` sends there. Needed only when `host` is not PostHog Cloud, for example a reverse proxy, which must not receive the secret key. | `None` -`email_request_timeout_seconds` | `float` | Timeout in seconds for each email send request. Defaults to 15. | `15` -`email_request_max_retries` | `int` | Number of retries for an email send after a network error, a rate limit, or an unavailable service. Defaults to 2. Set to 0 when a job runner already retries the send. | `2` -`super_properties` | | Properties for every captured event. They fill only keys the event leaves unset, before `before_send` runs. `$set`, `$set_once`, `$groups` and `$group_set` fill one level deep. An event's own properties and context tags override them, and `before_send` can change or remove them. | `None` -`super_options` | | Capture options for every captured event, such as `{"cookieless_mode": True}`. They fill only options the event leaves unset, before `before_send` runs. An event's own `options` and context options override them, and `before_send` can change them. They also win over an event's legacy property for the same key, such as `$cookieless_mode`, so pass per-event overrides of that key as `options`. | `None` -`enable_exception_autocapture` | | Automatically capture uncaught exceptions. | `False` -`log_captured_exceptions` | | Also log exceptions captured by error tracking. | `False` -`project_root` | | Root path used to determine in-app stack frames for captured exceptions. Defaults to the current working directory. | `None` -`privacy_mode` | | For AI observability, capture usage metadata without prompt inputs or outputs. | `False` -`enable_full_ai_capture` | | Capture full AI content in PostHog AI wrapper events: skips string truncation and passes media (base64/data URIs) through unredacted. `privacy_mode` always wins. Wrapper events use the AI capture endpoint whether or not this is set. Defaults to False. | `False` -`before_send` | | Optional callback that can modify or drop events before upload. Return `None` to drop an event. Context tags, context options, `super_properties`, `super_options` and the values the SDK adds, such as `$is_server`, `$geoip_disable` and `$os`, fill in before it runs, so it can change or remove them. | `None` -`flag_fallback_cache_url` | | Optional feature flag fallback cache URL, such as `memory://local/?ttl=300&size=10000` or a Redis URL. | `None` -`enable_local_evaluation` | | Whether to poll feature flag definitions for local evaluation when a personal API key is configured. | `True` -`flag_definition_cache_provider` | `Optional[FlagDefinitionCacheProvider]` | Optional external cache provider for sharing feature flag definitions across workers. | `None` -`capture_exception_code_variables` | | Capture local variable values on exception stack frames. | `False` -`capture_trace_context` | | When OpenTelemetry is installed and a valid span is active at capture time, add its trace and span IDs as `$trace_id` and `$span_id` properties to events captured with `capture()` and `capture_ai()`, so they can be correlated with backend traces. They fill after context tags and `super_properties`, before `before_send`. A `$trace_id` or `$span_id` from the event, a context tag or `super_properties` stops the SDK adding either ID. Exception events (`capture_exception`) always attach these IDs regardless of this setting. Defaults to False. | `False` -`traces` | `Optional[dict]` | Config dict for distributed tracing: `service_name`, `service_version`, `environment`, `resource_attributes`, `flush_interval` (5 s), `max_queue_size` (2048), `max_export_batch_size` (512), `max_live_spans` (10000), `max_span_age` (3600 s), `max_attributes_per_span` (128), `max_events_per_span` (128), `max_attribute_value_length` (8192). `before_span_send` is a callable, or a list run in order, that receives each finished span as a dict (`trace_id`, `span_id` and `parent_span_id` are read-only) and returns it, edited, or `None` to drop it; a hook that raises drops the span. Tracing is off until this is provided. Spans export on a background timer even with `sync_mode`; serverless handlers should call `flush()` before returning. Defaults to None. | `None` -`code_variables_mask_patterns` | | Variable-name patterns to mask when capturing code variables. | `None` -`code_variables_ignore_patterns` | | Variable-name patterns to omit when capturing code variables. | `None` -`code_variables_mask_url_credentials` | | Scrub credentials embedded in URLs/DSNs (e.g. `user:pass@host`) from captured code variables, regardless of the surrounding variable name. Defaults to True. | `None` -`code_variables_detect_secrets` | | Last-resort entropy-based detection that redacts high-entropy secret-looking values (API keys, tokens, strong passwords) sitting in innocuously-named variables, after the name and URL checks. Skips structured ids (UUIDs, ObjectIds, hashes). Defaults to True. | `None` -`in_app_modules` | `list[str] \| None` | Module/package prefixes treated as in-app frames in captured exceptions. | `None` -`enable_exception_autocapture_rate_limiting` | | Rate limit autocaptured exceptions client-side with a token bucket per exception type. Disabled by default. | `False` -`exception_autocapture_bucket_size` | | Maximum burst of autocaptured exceptions allowed per exception type (token bucket size, clamped to 0-100). | `DEFAULT_BUCKET_SIZE` -`exception_autocapture_refill_rate` | | Tokens restored per refill interval for each exception type's bucket. | `DEFAULT_REFILL_RATE` -`exception_autocapture_refill_interval_seconds` | | Seconds between token refills for autocaptured exception rate limiting. | `DEFAULT_REFILL_INTERVAL_SECONDS` -`capture_compression` | `Optional[Union[CaptureCompression, str]]` | Request-body compression for `capture()` uploads. `CaptureCompression.GZIP` or `DEFLATE` (or the strings `"gzip"`/`"deflate"`). When omitted, the `POSTHOG_CAPTURE_COMPRESSION` env var is consulted, then no compression. | `None` -`capture_ai_compression` | `Optional[Union[CaptureCompression, str]]` | Request-body compression for `capture_ai()` uploads, set independently of `capture_compression`. Defaults to no compression, and the env var does not apply. `CaptureCompression.ZSTD` suits large AI payloads. | `None` -`capture_ai_max_queue_size` | `int` | Maximum number of AI events buffered before upload. Defaults to 1000, lower than `max_queue_size` because AI events are much larger. | `1000` -`capture_ai_timeout` | `float` | Seconds allowed for one AI upload request. Defaults to 30, longer than `timeout` because AI batches are much larger. | `30` -`capture_ai_max_event_bytes` | `int` | Largest serialized AI event the SDK sends; a larger one is dropped with an error log. Defaults to the AI endpoint's ceiling plus envelope headroom, and may only be lowered. | `AI_MAX_MSG_SIZE` - -**Examples:** - -```python -from posthog import Posthog - -posthog = Posthog('', host='') -``` - -## Shutdown - -### `Client.join` - -```python -join() -> None -``` - -Attempt to process queued events and end the consumer threads. Do not use directly, call `shutdown()` instead. - -Failed or undrainable events may be dropped and reported through logging -or `on_error`; returning does not guarantee server receipt. Lifecycle -cleanup is attempted once, and cleanup failures are logged without retry. - -**Examples:** - -```python -posthog.join() -``` - -### `Client.shutdown` - -```python -shutdown() -> None -``` - -Attempt final delivery and permanently shut down the client. - -Call this for final process cleanup or when disposing of a client that -will not be reused. It closes capture lanes, stops background workers and -the feature flag poller, and tears down integrations. Do not call it at the -end of each request or serverless invocation when the client is shared; -call `flush()` before returning instead. A shut-down client cannot be -reused; create a new client if needed. - -Normally this method blocks until queued events have been attempted and -cleanup finishes. Failed or undrainable events may be dropped and -reported through logging or `on_error`; returning does not guarantee -server receipt. Event draining has no timeout. Queued spans get one final -flush with up to 30 seconds waiting for an in-flight flush, then a fresh -30-second drain budget; the last HTTP request is not cut short. Any spans -it cannot send are discarded with a warning, as are spans still open. -Lifecycle cleanup is attempted once, and cleanup failures -are logged without retry. When called directly from an SDK callback such as -`on_error`, shutdown is deferred to avoid blocking the worker that -invoked the callback. If the callback must coordinate a blocking -shutdown, have it signal an -application-owned thread and return before that thread calls shutdown. -Do not wait inside the callback for another thread or task that calls a -lifecycle method. - -**Examples:** - -```python -posthog.shutdown() -``` - -## Flag definition loader - -### `Client.load_feature_flags` - -```python -load_feature_flags() -``` - -Load feature flags for local evaluation. - -**Examples:** - -```python -posthog.load_feature_flags() -``` - -### `Client.feature_flag_definitions` - -```python -feature_flag_definitions() -``` - -Return feature flag definitions loaded for local evaluation. - -**Returns:** - -Type | Description ----- | ----------- - | The currently loaded feature flag definitions, or `None` before - | local evaluation has loaded definitions. - -## SDK utils - -### `Client.queue` - -```python -queue: Queue -``` - -The analytics lane's queue (kept for backwards compatibility). - -### `Client.consumers` - -```python -consumers: Optional[List[Consumer]] -``` - -Flat list of the lanes' consumers, analytics first (kept for backwards compatibility). - -### `Client.new_context` - -```python -new_context(fresh = False, capture_exceptions: Optional[bool] = None) -``` - -Create a new context for managing shared state. Learn more about [contexts](/docs/libraries/python#contexts). - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`fresh` | | Whether to create a fresh context that doesn't inherit from parent. | `False` -`capture_exceptions` | `Optional[bool]` | Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting. | `None` - -**Examples:** - -```python -with client.new_context(): - client.identify_context('') - client.capture('event_name') -``` - -
-Category - -Contexts - -
- -### `Client.scoped` - -```python -scoped(fresh = False, capture_exceptions: Optional[bool] = None) -``` - -Decorator that creates a new context for the wrapped function using this client. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`fresh` | | Whether to create a fresh context that doesn't inherit from parent. | `False` -`capture_exceptions` | `Optional[bool]` | Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting. | `None` - -
-Category - -Contexts - -
- -### `Client.tag` - -```python -tag(name: str, value: Any) -> None -``` - -Add a tag to the current context. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`name` | `str` | The tag key. | *required* -`value` | `Any` | The tag value. | *required* - -
-Category - -Contexts - -
- -### `Client.get_tags` - -```python -get_tags() -> Dict[str, Any] -``` - -Get all tags from the current context. - -**Returns:** - -Type | Description ----- | ----------- -`Dict[str, Any]` | Dict of all tags in the current context. - -
-Category - -Contexts - -
- -### `Client.set_context_option` - -```python -set_context_option(key: str, value: Any) -> None -``` - -Set a capture option for every event captured within the current context. - -Context options fill options an event leaves unset, before -`before_send` runs, so the hook sees them. They override -`super_options`. An event's own `options` override them, and -`before_send` can change them. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The option name, such as `"process_person_profile"`. | *required* -`value` | `Any` | The option value, sent as given. | *required* - -
-Category - -Contexts - -
- -### `Client.get_context_options` - -```python -get_context_options() -> Dict[str, Any] -``` - -Get all capture options from the current context. - -**Returns:** - -Type | Description ----- | ----------- -`Dict[str, Any]` | Dict of all capture options in the current context. - -
-Category - -Contexts - -
- -### `Client.identify_context` - -```python -identify_context(distinct_id: str) -> None -``` - -Identify the current context with a distinct ID. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | `str` | The distinct ID to associate with the current context and its children. | *required* - -
-Category - -Contexts - -
- -### `Client.set_context_session` - -```python -set_context_session(session_id: str) -> None -``` - -Set the session ID for the current context. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`session_id` | `str` | The session ID to associate with the current context and its children. | *required* - -
-Category - -Contexts - -
- -### `Client.set_context_device_id` - -```python -set_context_device_id(device_id: str) -> None -``` - -Set the device ID for the current context. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`device_id` | `str` | The device ID to associate with the current context and its children. | *required* - -
-Category - -Contexts - -
- -### `Client.feature_flags` - -```python -feature_flags -``` - -Get the local evaluation feature flags. - -### `Client.get_feature_payloads` - -```python -get_feature_payloads(distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, disable_geoip: Optional[bool] = None, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None) -> dict[str, Optional[str]] -``` - -Get feature flag payloads for a user, preserving valid serialized JSON. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` -`flag_keys_to_evaluate` | `Optional[list[str]]` | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | `None` -`device_id` | `Optional[str]` | The device ID for this request. | `None` - -**Examples:** - -```python -payloads = posthog.get_feature_payloads('') -``` - -
-Category - -Feature flags - -
- -### `Client.get_flags_decision` - -```python -get_flags_decision(distinct_id: Optional[ID_TYPES] = None, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, disable_geoip: Optional[bool] = None, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None) -> FlagsResponse -``` - -Get feature flags decision. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | `Optional[ID_TYPES]` | The distinct ID of the user. | `None` -`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` -`flag_keys_to_evaluate` | `Optional[list[str]]` | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | `None` -`device_id` | `Optional[str]` | The device ID for this request. | `None` - -**Examples:** - -```python -decision = posthog.get_flags_decision('user123') -``` - -
-Category - -Feature flags - -
- -### `Client.metrics` - -```python -metrics: PostHogMetrics -``` - -The `posthog.metrics` API: a statsd-style pre-aggregating metrics client — alpha. - -Samples fold into per-series aggregates in memory and flush as one OTLP -data point per series per window, so recording from hot paths is cheap. -Configure via the `metrics` client option; pending metrics flush on -`shutdown()`. - -**Examples:** - -```python -client = Posthog("", metrics={"service_name": "billing-worker"}) -client.metrics.count("invoices.processed", 1, attributes={"plan": "pro"}) -client.metrics.gauge("queue.depth", 42) -client.metrics.histogram("job.duration", 187, unit="ms") -``` - -### `Client.get_remote_config_payload` - -```python -get_remote_config_payload(key: str) -``` - -Get the payload for a remote config feature flag. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The remote config feature flag key. | *required* - -**Returns:** - -Type | Description ----- | ----------- - | The payload associated with the feature flag, or `None` if the - | client is disabled, no personal API key is configured, or the request - | fails. Encrypted payloads are decrypted by PostHog before being - | returned. - -
-Note - -Requires `secret_key` for authentication. - -
- -
-Category - -Feature flags - -
- -### `Client.get_feature_flag_evaluation_runtime` - -```python -get_feature_flag_evaluation_runtime(key: str) -> Optional[FeatureFlagEvaluationRuntime] -``` - -Return where a locally loaded feature flag is meant to be evaluated. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The feature flag key. | *required* - -**Returns:** - -Type | Description ----- | ----------- -`Optional[FeatureFlagEvaluationRuntime]` | The flag's evaluation runtime, or `None` when local evaluation has -`Optional[FeatureFlagEvaluationRuntime]` | not loaded a definition for this key. A definition that carries no -`Optional[FeatureFlagEvaluationRuntime]` | runtime reports `FeatureFlagEvaluationRuntime.ALL`, the default -`Optional[FeatureFlagEvaluationRuntime]` | PostHog applies. - -**Examples:** - -```python -from posthog import FeatureFlagEvaluationRuntime - -runtime = posthog.get_feature_flag_evaluation_runtime("my-flag") -if runtime is FeatureFlagEvaluationRuntime.SERVER: - ... -``` - -
-Category - -Feature flags - -
- -### `Client.get_feature_flag_keys_by_evaluation_runtime` - -```python -get_feature_flag_keys_by_evaluation_runtime(evaluation_runtime: Union[FeatureFlagEvaluationRuntime, str]) -> list[str] -``` - -Return the keys of locally loaded flags that a runtime can evaluate. - -A flag set to `FeatureFlagEvaluationRuntime.ALL` suits either runtime, -so it is returned for `CLIENT` and for `SERVER`, and asking for -`ALL` returns every loaded flag. Use this to decide which flags to hand -to a browser when a backend serves flags to its own frontend. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`evaluation_runtime` | `Union[FeatureFlagEvaluationRuntime, str]` | The runtime to match, as a `FeatureFlagEvaluationRuntime` or its string value. | *required* - -**Returns:** - -Type | Description ----- | ----------- -`list[str]` | The matching flag keys, in the order local evaluation loaded them. -`list[str]` | Empty when no definitions are loaded. - -**Examples:** - -```python -from posthog import FeatureFlagEvaluationRuntime - -client_keys = posthog.get_feature_flag_keys_by_evaluation_runtime( - FeatureFlagEvaluationRuntime.CLIENT -) -``` - -
-Category - -Feature flags - -
- -### `posthog.integrations.django.PosthogContextMiddleware` - -```python -PosthogContextMiddleware(get_response) -``` - -Middleware to automatically track Django requests. - -This middleware wraps all calls with a posthog context. It attempts to extract the following from the request: -- Session ID, (extracted from `X-POSTHOG-SESSION-ID`) -- Distinct ID, (extracted from `X-POSTHOG-DISTINCT-ID`, falling back to the authenticated request user ID) -- Authenticated user email as `email` -- Request URL as `$current_url` -- Request method as `$request_method` -- Request path as `$request_path` -- Forwarded IP address as `$ip` -- User agent as `$user_agent` - -Configure the integration with these Django settings: -- `POSTHOG_MW_CLIENT`: client used for automatic exception capture, or the global client. - This does not redirect events explicitly captured through other clients. -- `POSTHOG_MW_CAPTURE_EXCEPTIONS`: `False` enables context enrichment only; - `True` enables automatic exception capture; `None` inherits the effective client's - `enable_exception_autocapture`. When omitted, the legacy default is `True`. -- `POSTHOG_MW_REQUEST_FILTER`: a callback receiving the request. Returning `False` - disables both enrichment and automatic exception capture for that request. -- `POSTHOG_MW_EXTRA_PROPERTIES`: a callback returning additional event properties. -- `POSTHOG_MW_CAPTURE_TRACING_HEADERS`: read PostHog identity and session headers - as request-scoped analytics context (default: `True`). Explicit capture IDs and - event properties take precedence. These values are never authentication or - authorization. Authenticated Django user identity remains the fallback when no - usable header ID is available or header capture is disabled. -- `POSTHOG_MW_TRUST_TRACING_HEADERS`: deprecated alias for - `POSTHOG_MW_CAPTURE_TRACING_HEADERS`; the capture-named setting takes precedence. -- `POSTHOG_MW_PROPERTIES_MAP`: an optional callback transforming request properties. - -The old `POSTHOG_MW_EXTRA_TAGS` and `POSTHOG_MW_TAG_MAP` settings remain aliases; -the property-named settings take precedence when both are configured. - -Request properties are included on all events captured within the active context, -including when automatic exception capture is disabled. -See the context documentation for more information. The extracted distinct ID and session ID, -if found, are used to associate all events captured in the middleware context with the same distinct ID -and session as currently active on the frontend. See the documentation for `set_context_session` -and `identify_context` for more details. - -This middleware is hybrid-capable: it supports both WSGI (sync) and ASGI (async) Django applications. The middleware -detects at initialization whether the next middleware in the chain is async or sync, and adapts its behavior accordingly. -This ensures compatibility with both pure sync and pure async middleware chains, as well as mixed chains in ASGI mode. - -**Functions:** - -Name | Description ----- | ----------- -`aextract_properties` | Async version of extract_properties for use in async request handling. -`aextract_request_user` | Async version of extract_request_user for use in async request handling. -`aextract_tags` | Compatibility alias for `aextract_properties`. -`extract_properties` | Extract event properties and identity from a synchronous request. -`extract_request_user` | Extract user ID and email from request in sync context. -`extract_tags` | Compatibility alias for `extract_properties`. -`process_exception` | Process exceptions from views and downstream middleware. - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`async_capable` | | -`capture_exceptions` | `Optional[bool]` | -`capture_tracing_headers` | | -`client` | | -`extra_properties` | | -`extra_tags` | | Compatibility alias for `extra_properties`. -`get_response` | | -`properties_map` | | -`request_filter` | | -`sync_capable` | | -`tag_map` | | Compatibility alias for `properties_map`. -`trust_tracing_headers` | `bool` | Deprecated alias for `capture_tracing_headers`. - -Initialize the middleware with Django's next handler. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`get_response` | | The next middleware or view handler in Django's middleware chain. May be synchronous or asynchronous. | - -#### `posthog.integrations.django.PosthogContextMiddleware.aextract_properties` - -```python -aextract_properties(request) -``` - -Async version of extract_properties for use in async request handling. - -Uses await request.auser() instead of request.user to avoid -SynchronousOnlyOperation in async context. - -Follows Django's naming convention for async methods (auser, asave, etc.). - -#### `posthog.integrations.django.PosthogContextMiddleware.aextract_request_user` - -```python -aextract_request_user(request) -``` - -Async version of extract_request_user for use in async request handling. - -Uses await request.auser() instead of request.user to avoid -SynchronousOnlyOperation in async context. - -Follows Django's naming convention for async methods (auser, asave, etc.). - -#### `posthog.integrations.django.PosthogContextMiddleware.aextract_tags` - -```python -aextract_tags(request) -``` - -Compatibility alias for `aextract_properties`. - -#### `posthog.integrations.django.PosthogContextMiddleware.async_capable` - -```python -async_capable = True -``` - -#### `posthog.integrations.django.PosthogContextMiddleware.capture_exceptions` - -```python -capture_exceptions: Optional[bool] = capture_exceptions if (capture_exceptions is None or isinstance(capture_exceptions, bool)) else True -``` - -#### `posthog.integrations.django.PosthogContextMiddleware.capture_tracing_headers` - -```python -capture_tracing_headers = _resolve_capture_tracing_headers(capture_tracing_headers, trust_tracing_headers, canonical_name='POSTHOG_MW_CAPTURE_TRACING_HEADERS', deprecated_name='POSTHOG_MW_TRUST_TRACING_HEADERS') -``` - -#### `posthog.integrations.django.PosthogContextMiddleware.client` - -```python -client = cast('Optional[Client]', settings.POSTHOG_MW_CLIENT) -``` - -#### `posthog.integrations.django.PosthogContextMiddleware.extra_properties` - -```python -extra_properties = cast('Callable[[HttpRequest], Dict[str, Any]]', extra_properties) if callable(extra_properties) else None -``` - -#### `posthog.integrations.django.PosthogContextMiddleware.extra_tags` - -```python -extra_tags -``` - -Compatibility alias for `extra_properties`. - -#### `posthog.integrations.django.PosthogContextMiddleware.extract_properties` - -```python -extract_properties(request) -``` - -Extract event properties and identity from a synchronous request. - -#### `posthog.integrations.django.PosthogContextMiddleware.extract_request_user` - -```python -extract_request_user(request) -``` - -Extract user ID and email from request in sync context. - -#### `posthog.integrations.django.PosthogContextMiddleware.extract_tags` - -```python -extract_tags(request) -``` - -Compatibility alias for `extract_properties`. - -#### `posthog.integrations.django.PosthogContextMiddleware.get_response` - -```python -get_response = get_response -``` - -#### `posthog.integrations.django.PosthogContextMiddleware.process_exception` - -```python -process_exception(request, exception) -``` - -Process exceptions from views and downstream middleware. - -Django calls this WHILE still inside the context created by __call__, -so request tags have already been extracted and set. This method just -needs to capture the exception directly. - -Django converts view exceptions into responses before they propagate through -the middleware stack, so the context manager in __call__/__acall__ never sees them. - -Note: Django's process_exception is always synchronous, even for async views. - -#### `posthog.integrations.django.PosthogContextMiddleware.properties_map` - -```python -properties_map = cast('Callable[[Dict[str, Any]], Dict[str, Any]]', properties_map) if callable(properties_map) else None -``` - -#### `posthog.integrations.django.PosthogContextMiddleware.request_filter` - -```python -request_filter = cast('Optional[Callable[[HttpRequest], bool]]', settings.POSTHOG_MW_REQUEST_FILTER) -``` - -#### `posthog.integrations.django.PosthogContextMiddleware.sync_capable` - -```python -sync_capable = True -``` - -#### `posthog.integrations.django.PosthogContextMiddleware.tag_map` - -```python -tag_map -``` - -Compatibility alias for `properties_map`. - -#### `posthog.integrations.django.PosthogContextMiddleware.trust_tracing_headers` - -```python -trust_tracing_headers: bool -``` - -Deprecated alias for `capture_tracing_headers`. - -### `posthog.integrations.flask.PosthogFlaskIntegration` - -```python -PosthogFlaskIntegration(app: Optional[Flask] = None, *, client: Optional[Client] = None, capture_exceptions: Optional[bool] = None, request_filter: Optional[Callable[[Request], bool]] = None, extra_properties: Optional[Callable[[Request], Mapping[str, Any]]] = None, trust_tracing_headers: Optional[bool] = None, capture_tracing_headers: Optional[bool] = None) -> None -``` - -Add PostHog request context and exception tracking to a Flask app. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`app` | `Optional[Flask]` | An optional Flask application. If omitted, call `init_app` later (the Flask application-factory pattern). | `None` -`client` | `Optional[Client]` | Optional destination for automatically captured exceptions. The global client is used by default. Request context still enriches events captured through any client and does not redirect capture calls. | `None` -`capture_exceptions` | `Optional[bool]` | Capture exceptions that reach Flask's unhandled exception machinery. If omitted, inherit the effective client's `enable_exception_autocapture` setting. Pass `True` or `False` to override it. | `None` -`request_filter` | `Optional[Callable[[Request], bool]]` | Optional callback receiving Flask's request object. A false return value disables both context and exception capture for that request. | `None` -`extra_properties` | `Optional[Callable[[Request], Mapping[str, Any]]]` | Optional callback returning additional event properties. This is useful for application-specific metadata such as an authenticated user's role. Values should not contain secrets or request bodies. | `None` -`capture_tracing_headers` | `Optional[bool]` | Read PostHog distinct and session ID headers as request-scoped analytics context. Defaults to True. Explicit capture IDs and event properties take precedence. Never use these values for security. | `None` -`trust_tracing_headers` | `Optional[bool]` | Deprecated alias for `capture_tracing_headers`. The capture-named option takes precedence when both are supplied. | `None` - -The integration intentionally does not capture exceptions handled by an -application error handler, expected HTTP exceptions, request or response -bodies, query strings, cookies, authorization headers, or arbitrary headers. -Call `capture_exception` explicitly from a custom error handler if a -handled exception should be reported. - -**Functions:** - -Name | Description ----- | ----------- -`init_app` | Register the integration with a Flask application once. - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`capture_exceptions` | | -`capture_tracing_headers` | | -`client` | | -`extra_properties` | | -`request_filter` | | -`trust_tracing_headers` | `bool` | Deprecated alias for `capture_tracing_headers`. - -#### `posthog.integrations.flask.PosthogFlaskIntegration.capture_exceptions` - -```python -capture_exceptions = capture_exceptions -``` - -#### `posthog.integrations.flask.PosthogFlaskIntegration.capture_tracing_headers` - -```python -capture_tracing_headers = _resolve_capture_tracing_headers(capture_tracing_headers, trust_tracing_headers) -``` - -#### `posthog.integrations.flask.PosthogFlaskIntegration.client` - -```python -client = client -``` - -#### `posthog.integrations.flask.PosthogFlaskIntegration.extra_properties` - -```python -extra_properties = extra_properties -``` - -#### `posthog.integrations.flask.PosthogFlaskIntegration.init_app` - -```python -init_app(app: Flask) -> None -``` - -Register the integration with a Flask application once. - -#### `posthog.integrations.flask.PosthogFlaskIntegration.request_filter` - -```python -request_filter = request_filter -``` - -#### `posthog.integrations.flask.PosthogFlaskIntegration.trust_tracing_headers` - -```python -trust_tracing_headers: bool -``` - -Deprecated alias for `capture_tracing_headers`. - -### `posthog.integrations.asgi.PosthogASGIMiddleware` - -```python -PosthogASGIMiddleware(app: _ASGIApp, client: Optional[Client] = None, capture_exceptions: Optional[bool] = None, request_filter: Optional[_RequestFilter] = None, extra_properties: Optional[_ExtraProperties] = None, trust_tracing_headers: Optional[bool] = None, *, capture_tracing_headers: Optional[bool] = None) -> None -``` - -Add PostHog context and automatic exception capture to an ASGI app. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`app` | `_ASGIApp` | The downstream ASGI application. | *required* -`client` | `Optional[Client]` | Optional destination for automatically captured exceptions. The global client is used by default. Request context still enriches events captured through any client and does not redirect capture calls. | `None` -`capture_exceptions` | `Optional[bool]` | Capture exceptions escaping the downstream app. If omitted, inherit the effective client's `enable_exception_autocapture` setting. Pass `True` or `False` to override it. | `None` -`request_filter` | `Optional[_RequestFilter]` | Optional sync or async callback receiving the ASGI scope. Returning `False` bypasses all instrumentation for that scope. | `None` -`extra_properties` | `Optional[_ExtraProperties]` | Optional sync or async callback receiving the ASGI scope and returning additional event properties. | `None` -`capture_tracing_headers` | `Optional[bool]` | Read PostHog distinct and session ID headers as request-scoped analytics context. Defaults to True. Explicit capture IDs and event properties take precedence. Never use these values for security. | `None` -`trust_tracing_headers` | `Optional[bool]` | Deprecated alias for `capture_tracing_headers`. The capture-named option takes precedence when both are supplied. | `None` - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`app` | | -`capture_exceptions` | | -`capture_tracing_headers` | | -`client` | | -`extra_properties` | | -`request_filter` | | -`trust_tracing_headers` | `bool` | Deprecated alias for `capture_tracing_headers`. - -#### `posthog.integrations.asgi.PosthogASGIMiddleware.app` - -```python -app = app -``` - -#### `posthog.integrations.asgi.PosthogASGIMiddleware.capture_exceptions` - -```python -capture_exceptions = capture_exceptions -``` - -#### `posthog.integrations.asgi.PosthogASGIMiddleware.capture_tracing_headers` - -```python -capture_tracing_headers = _resolve_capture_tracing_headers(capture_tracing_headers, trust_tracing_headers) -``` - -#### `posthog.integrations.asgi.PosthogASGIMiddleware.client` - -```python -client = client -``` - -#### `posthog.integrations.asgi.PosthogASGIMiddleware.extra_properties` - -```python -extra_properties = extra_properties -``` - -#### `posthog.integrations.asgi.PosthogASGIMiddleware.request_filter` - -```python -request_filter = request_filter -``` - -#### `posthog.integrations.asgi.PosthogASGIMiddleware.trust_tracing_headers` - -```python -trust_tracing_headers: bool -``` - -Deprecated alias for `capture_tracing_headers`. - -### `posthog.integrations.celery.PosthogCeleryIntegration` - -```python -PosthogCeleryIntegration(client: Optional[Client] = None, capture_exceptions: bool = True, capture_task_lifecycle_events: bool = True, propagate_context: bool = True, task_filter: Optional[Callable[[Optional[str], dict[str, Any]], bool]] = None) -``` - -Celery integration that captures task lifecycle events and exceptions. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`client` | `Optional[Client]` | Optional `Client` instance. When provided, all events and exceptions are captured through this client rather than the global `posthog` module. Don't skip this if using a custom flag definition cache provider, and pass the custom `Client` instance here initialized with the custom provider so fork safety for that provider is handled correctly. | `None` -`capture_exceptions` | `bool` | Whether to capture task exceptions via `capture_exception` (default `True`). | `True` -`capture_task_lifecycle_events` | `bool` | Whether to emit lifecycle events of the task such as "started", "success", "failure" etc. (default `True`). | `True` -`propagate_context` | `bool` | Whether to propagate PostHog context (distinct ID, session ID, tags) from the producer to the worker via task headers (default `True`). | `True` -`task_filter` | `Optional[Callable[[Optional[str], dict[str, Any]], bool]]` | Optional callback `(task_name, task_properties) -> bool` expected to return `False` if a given task should not be tracked. | `None` - -**Functions:** - -Name | Description ----- | ----------- -`instrument` | Connect Celery signal handlers to capture task events and exceptions. -`shutdown` | Disconnect all signal handlers registered by `instrument()`, flush all pending events -`uninstrument` | Disconnect Celery signal handlers and unregister exit cleanup. - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`capture_exceptions` | | -`capture_task_lifecycle_events` | | -`client` | | -`propagate_context` | | -`task_filter` | | - -#### `posthog.integrations.celery.PosthogCeleryIntegration.capture_exceptions` - -```python -capture_exceptions = capture_exceptions -``` - -#### `posthog.integrations.celery.PosthogCeleryIntegration.capture_task_lifecycle_events` - -```python -capture_task_lifecycle_events = capture_task_lifecycle_events -``` - -#### `posthog.integrations.celery.PosthogCeleryIntegration.client` - -```python -client = client -``` - -#### `posthog.integrations.celery.PosthogCeleryIntegration.instrument` - -```python -instrument() -> None -``` - -Connect Celery signal handlers to capture task events and exceptions. -Call this after initializing the PostHog client and this integration. - -If Celery runs on a single host, reinstrumenting in worker children is -not strictly necessary because the PostHog client and this integration -are fork-safe. If Celery workers run across multiple hosts, each worker -process must initialize PostHog, this integration, and call -`instrument()`. Celery provides `worker_process_init` signal to help -with this. - -#### `posthog.integrations.celery.PosthogCeleryIntegration.propagate_context` - -```python -propagate_context = propagate_context -``` - -#### `posthog.integrations.celery.PosthogCeleryIntegration.shutdown` - -```python -shutdown() -> None -``` - -Disconnect all signal handlers registered by `instrument()`, flush all pending events -and cleanly shutdown the integration. - -`shutdown()` is also registered on `worker_process_shutdown` and `atexit` signals, -but there is no guarantee those will always be called, so we strongly recommend calling -it manually when the integration is no longer needed to avoid data loss. - -#### `posthog.integrations.celery.PosthogCeleryIntegration.task_filter` - -```python -task_filter = task_filter -``` - -#### `posthog.integrations.celery.PosthogCeleryIntegration.uninstrument` - -```python -uninstrument() -> None -``` - -Disconnect Celery signal handlers and unregister exit cleanup. - -Do not use directly, call `shutdown()` instead. - -### `posthog.request.set_socket_options` - -```python -set_socket_options(socket_options: Optional[SocketOptions]) -> None -``` - -Configure socket options for all SDK HTTP connections. - -Call this during initialization, before making API requests. Pass `None` -to reset to the default socket behavior. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`socket_options` | `Optional[SocketOptions]` | A list of `(level, option, value)` tuples accepted by urllib3/`socket.setsockopt()`, or `None` to reset defaults. | *required* - -
-Example - -from posthog import set_socket_options -set_socket_options([(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)]) - -
- -### `posthog.request.enable_keep_alive` - -```python -enable_keep_alive() -> None -``` - -Enable TCP keepalive for SDK HTTP connections. - -This helps prevent idle pooled connections from being dropped by network -infrastructure. Call during initialization, before making API requests. - -### `posthog.request.disable_connection_reuse` - -```python -disable_connection_reuse() -> None -``` - -Disable HTTP connection reuse for SDK requests. - -Each request will create a fresh connection. This can avoid issues with -environments that terminate pooled connections, but adds per-request -overhead. Call during initialization, before making API requests. - -## AsyncClient - -Needs the `async` extra, `posthog[async]`. - -### `AsyncClient.alias` - -```python -alias(previous_id: ID_TYPES, distinct_id: Optional[str], timestamp: Optional[Union[datetime, str]] = None, uuid: Optional[str] = None, disable_geoip: Optional[bool] = None, options: Optional[Dict[str, Any]] = None) -> Optional[str] -``` - -### `AsyncClient.capture` - -```python -capture(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] -``` - -Queue an event without blocking for network delivery. - -### `AsyncClient.capture_immediate` - -```python -capture_immediate(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] -``` - -Capture one event and wait until its delivery attempt completes. - -### `AsyncClient.capture_ai` - -```python -capture_ai(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] -``` - -Queue an AI event for the AI capture endpoint without blocking. - -Takes the same arguments and returns the same value as `capture()`. -The event uses a separate queue with a larger per-event size cap. The -payload is sent as given, with no redaction or truncation. - -### `AsyncClient.capture_ai_immediate` - -```python -capture_ai_immediate(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] -``` - -Capture one AI event and wait until its delivery attempt completes. - -Uses the AI capture endpoint and the `capture_ai_*` settings. - -### `AsyncClient.capture_exception` - -```python -capture_exception(exception: Optional[ExceptionArg] = None, *, level: Optional[str] = None, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] -``` - -Capture an exception. This method never raises, including in debug mode. - -`level` sets `$exception_level` and defaults to `"error"`. An unknown -value counts as unset. Reserved exception properties in `properties`, -such as `$exception_list` and `$exception_level`, are ignored. - -When OpenTelemetry is installed and a valid span is active, its trace and -span IDs are added as `$trace_id` and `$span_id` event properties, -unless the event, a context tag or `super_properties` sets either one. - -### `AsyncClient.emails` - -```python -emails: AsyncEmails -``` - -Send transactional email from PostHog email templates, and read the -status of each email sent with `get`. - -Needs `secret_key`, a project secret API key with the `hog_flow:write` -scope, and `posthog[async]`. Sends go to `app_host`, or to the PostHog -Cloud app host of `host`. `email_request_timeout_seconds` and -`email_request_max_retries` configure each send. A disabled or shut-down -client sends nothing. See `posthog.emails` for setup. - -**Examples:** - -```python -await client.emails.send( - to="user@example.com", - template_id="0190f6d2-7c1e-7b3a-9f5e-1c2d3e4f5a6b", - distinct_id="user_123", -) -``` - -### `AsyncClient.flush` - -```python -flush(timeout_seconds: Optional[float] = 10) -> None -``` - -### `AsyncClient.evaluate_flags` - -```python -evaluate_flags(distinct_id: Optional[ID_TYPES] = None, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, disable_geoip: Optional[bool] = None, flag_keys: Optional[list[str]] = None, device_id: Optional[str] = None) -> FeatureFlagEvaluations -``` - -Evaluate flags remotely without blocking the event loop. - -The returned snapshot has synchronous in-memory accessors. Pass it to -`capture(flags=...)` to attach the exact values used for branching. -This client does not evaluate flags locally, so the snapshot's -`unresolved_flags` is always empty. - -### `AsyncClient.group_identify` - -```python -group_identify(group_type: str, group_key: str, properties: Optional[Dict[str, Any]] = None, timestamp: Optional[Union[datetime, str]] = None, uuid: Optional[Union[str, UUID]] = None, disable_geoip: Optional[bool] = None, distinct_id: Optional[ID_TYPES] = None, options: Optional[Dict[str, Any]] = None) -> Optional[str] -``` - -### `AsyncClient.set` - -```python -set(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str] -``` - -### `AsyncClient.set_once` - -```python -set_once(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str] -``` - -### `AsyncClient(...)` - -```python -__init__(project_api_key: str, host: Optional[str] = None, *, debug: bool = False, max_queue_size: int = 10000, send: bool = True, on_error: bool = None, flush_at: int = 100, flush_interval: float = 5.0, max_retries: int = 3, timeout: float = 15, thread: int = 1, disabled: bool = False, disable_geoip: bool = True, is_server: bool = True, historical_migration: bool = False, super_properties: Optional[dict[str, Any]] = None, super_options: Optional[dict[str, Any]] = None, before_send: Optional[dict[str, Any]] = None, log_captured_exceptions: bool = False, project_root: Optional[str] = None, capture_exception_code_variables: bool = False, code_variables_mask_patterns: bool = None, code_variables_ignore_patterns: bool = None, code_variables_mask_url_credentials: bool = None, code_variables_detect_secrets: bool = None, in_app_modules: Optional[list[str]] = None, capture_compression: Optional[Union[CaptureCompression, str]] = None, capture_ai_compression: Optional[Union[CaptureCompression, str]] = None, capture_ai_max_queue_size: int = 1000, capture_ai_timeout: float = 30, capture_ai_max_event_bytes: int = AI_MAX_MSG_SIZE, privacy_mode: bool = False, enable_full_ai_capture: bool = False, capture_trace_context: bool = False, secret_key: Optional[str] = None, personal_api_key: Optional[str] = None, feature_flags_request_timeout_seconds: float = 3, feature_flags_request_max_retries: int = 1, app_host: Optional[str] = None, email_request_timeout_seconds: float = 15, email_request_max_retries: int = 2) -> None -``` - -### `AsyncClient.shutdown` - -```python -shutdown() -> None -``` - -### `AsyncClient.join` - -```python -join() -> None -``` - -### `AsyncClient.get_remote_config_payload` - -```python -get_remote_config_payload(key: str) -> Optional[Any] -``` - -Fetch and decrypt a remote-config payload without blocking the loop. - -## Module functions - -### `posthog.new_context` - -```python -new_context(fresh: bool = False, capture_exceptions: Optional[bool] = None, client: Optional[Client] = None) -``` - -Create a new context scope that will be active for the duration of the with block. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`fresh` | `bool` | Whether to start with a fresh context (default: False) | `False` -`capture_exceptions` | `Optional[bool]` | Whether to capture exceptions raised within the context. If omitted, defaults to the relevant client's exception autocapture setting. | `None` -`client` | `Optional[Client]` | Optional Posthog client instance to use for this context (default: None) | `None` - -**Examples:** - -```python -from posthog import new_context, tag, capture -with new_context(): - tag("request_id", "123") - capture("event_name", properties={"property": "value"}) -``` - -
-Category - -Contexts - -
- -### `posthog.scoped` - -```python -scoped(fresh = False, capture_exceptions: Optional[bool] = None) -``` - -Decorator that creates a new context for the function. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`fresh` | | Whether to start with a fresh context (default: False) | `False` -`capture_exceptions` | `Optional[bool]` | Whether to capture and track exceptions with posthog error tracking. If omitted, defaults to the global exception autocapture setting. | `None` - -**Examples:** - -```python -from posthog import scoped, tag, capture -@scoped() -def process_payment(payment_id): - tag("payment_id", payment_id) - capture("payment_started") -``` - -
-Category - -Contexts - -
- -### `posthog.set_context_session` - -```python -set_context_session(session_id: str) -``` - -Set the session ID for the current context. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`session_id` | `str` | The session ID to associate with the current context and its children | *required* - -**Examples:** - -```python -from posthog import set_context_session -set_context_session("session_123") -``` - -
-Category - -Contexts - -
- -### `posthog.set_context_device_id` - -```python -set_context_device_id(device_id: str) -``` - -Set the device ID for the current context, associating all feature flag requests -in this or child contexts with the given device ID. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`device_id` | `str` | The device ID to associate with the current context and its children | *required* - -**Examples:** - -```python -from posthog import set_context_device_id -set_context_device_id("device_123") -``` - -
-Category - -Contexts - -
- -### `posthog.identify_context` - -```python -identify_context(distinct_id: str) -``` - -Identify the current context with a distinct ID. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | `str` | The distinct ID to associate with the current context and its children | *required* - -**Examples:** - -```python -from posthog import identify_context -identify_context("user_123") -``` - -
-Category - -Identification - -
- -### `posthog.set_capture_exception_code_variables_context` - -```python -set_capture_exception_code_variables_context(enabled: bool) -``` - -Override code-variable capture for exceptions in the current context. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`enabled` | `bool` | Whether exceptions captured in this context should include local variable values from stack frames. | *required* - -
-Category - -Contexts - -
- -### `posthog.set_code_variables_mask_patterns_context` - -```python -set_code_variables_mask_patterns_context(mask_patterns: list[str]) -``` - -Override code-variable mask patterns for exceptions in the current context. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`mask_patterns` | `list[str]` | Variable-name patterns whose values should be replaced with `***` when code variables are captured. | *required* - -
-Category - -Contexts - -
- -### `posthog.set_code_variables_ignore_patterns_context` - -```python -set_code_variables_ignore_patterns_context(ignore_patterns: list[str]) -``` - -Override code-variable ignore patterns for exceptions in the current context. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`ignore_patterns` | `list[str]` | Variable-name patterns that should be omitted entirely when code variables are captured. | *required* - -
-Category - -Contexts - -
- -### `posthog.set_code_variables_mask_url_credentials_context` - -```python -set_code_variables_mask_url_credentials_context(enabled: bool) -``` - -Whether to scrub credentials embedded in URLs/DSNs (e.g. user:pass@host) from -captured code variables for the current context. - -### `posthog.set_code_variables_detect_secrets_context` - -```python -set_code_variables_detect_secrets_context(enabled: bool) -``` - -Whether to apply entropy-based secret detection as a last-resort redaction of -high-entropy values (API keys, tokens, strong passwords) in captured code -variables for the current context. - -
-Category - -Contexts - -
- -### `posthog.tag` - -```python -tag(name: str, value: Any) -``` - -Add a tag to the current context. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`name` | `str` | The tag key | *required* -`value` | `Any` | The tag value | *required* - -**Examples:** - -```python -from posthog import tag -tag("user_id", "123") -``` - -
-Category - -Contexts - -
- -### `posthog.get_tags` - -```python -get_tags() -> Dict[str, Any] -``` - -Get all tags from the current context. - -**Returns:** - -Type | Description ----- | ----------- -`Dict[str, Any]` | Dict of all tags in the current context - -
-Category - -Contexts - -
- -### `posthog.set_context_option` - -```python -set_context_option(key: str, value: Any) -> None -``` - -Set a capture option for every event captured within the current context. - -Context options fill options an event leaves unset, before `before_send` -runs, so the hook sees them. They override `super_options`. An event's -own `options` override them, and `before_send` can change them. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The option name, such as `"process_person_profile"` | *required* -`value` | `Any` | The option value, sent as given | *required* - -**Examples:** - -```python -from posthog import new_context, set_context_option -with new_context(): - set_context_option("process_person_profile", False) -``` - -
-Category - -Contexts - -
- -### `posthog.get_context_options` - -```python -get_context_options() -> Dict[str, Any] -``` - -Get all capture options from the current context. - -**Returns:** - -Type | Description ----- | ----------- -`Dict[str, Any]` | Dict of all capture options in the current context - -
-Category - -Contexts - -
- -### `posthog.capture` - -```python -capture(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] -``` - -Capture anything a user does within your system. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`event` | `str` | The event name to specify the event | *required* -`**kwargs` | `Unpack[OptionalCaptureArgs]` | Optional arguments including: distinct_id: Unique identifier for the user properties: Dict of event properties timestamp: When the event occurred. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. uuid: Unique identifier for this event. If omitted, one is generated and returned. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. groups: Dict of group types and IDs flags: A FeatureFlagEvaluations snapshot from evaluate_flags(). The exact values from the snapshot are attached with no extra /flags request. send_feature_flags: Deprecated. Prefer flags=... from evaluate_flags(). When truthy, evaluates flags during capture and attaches them to the event. disable_geoip: Whether to disable GeoIP lookup | `{}` - -
-Details - -Capture allows you to capture anything a user does within your system, which you can later use in PostHog to find patterns in usage, work out which features to improve or where people are giving up. A capture call requires an event name to specify the event. We recommend using [verb] [noun], like `movie played` or `movie updated` to easily identify what your events mean later on. Capture takes a number of optional arguments, which are defined by the `OptionalCaptureArgs` type. - -
- -**Examples:** - -```python -# Context and capture usage -from posthog import new_context, identify_context, tag_context, capture -# Enter a new context (e.g. a request/response cycle, an instance of a background job, etc) -with new_context(): - # Associate this context with some user, by distinct_id - identify_context('some user') - - # Capture an event, associated with the context-level distinct ID ('some user') - capture('movie started') - - # Capture an event associated with some other user (overriding the context-level distinct ID) - capture('movie joined', distinct_id='some-other-user') - - # Capture an event with some properties - capture('movie played', properties={'movie_id': '123', 'category': 'romcom'}) - - # Capture an event with some properties - capture('purchase', properties={'product_id': '123', 'category': 'romcom'}) - # Capture an event with some associated group - capture('purchase', groups={'company': 'id:5'}) - - # Adding a tag to the current context will cause it to appear on all subsequent events - tag_context('some-tag', 'some-value') - - capture('another-event') # Will be captured with `'some-tag': 'some-value'` in the properties dict -``` -```python -# Set event properties -from posthog import capture -capture( - "user_signed_up", - distinct_id="distinct_id_of_the_user", - properties={ - "login_type": "email", - "is_free_trial": "true" - } -) -``` - -### `posthog.capture_ai` - -```python -capture_ai(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] -``` - -Capture an AI event on the dedicated AI capture endpoint. - -Beta: the signature is stable; operational limits (per-event size cap, -batching, endpoint) may change without notice. - -Takes the same arguments and returns the same value as `capture()`: the -event UUID, or None when the event was not admitted (disabled client, or -dropped by `before_send`). The event is delivered on an isolated queue -with its own consumer pool and a higher per-event size cap, posting to -the dedicated AI ingestion endpoint. The payload is sent as given — no -redaction or truncation is applied here. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`event` | `str` | The event name, normally one of the `$ai_*` event names. | *required* -`**kwargs` | `Unpack[OptionalCaptureArgs]` | Same optional arguments as `capture()`. | `{}` - -**Examples:** - -```python -from posthog import capture_ai - -uuid = capture_ai( - "$ai_generation", - distinct_id="user_123", - properties={"$ai_model": "gpt-5"}, -) -``` - -### `posthog.set` - -```python -set(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str] -``` - -Set properties on a user record. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`**kwargs` | `Unpack[OptionalSetArgs]` | Optional arguments including: distinct_id: Unique identifier for the user. Falls back to the context distinct ID; if none exists, this call does nothing. properties: Dict of person properties to set. timestamp: When the properties were set. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. uuid: Unique identifier for this operation. If omitted, one is generated and returned. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. disable_geoip: Whether to disable GeoIP lookup. | `{}` - -
-Details - -This will overwrite previous people property values. Generally operates similar to `capture`, with distinct_id being an optional argument, defaulting to the current context's distinct ID. If there is no context-level distinct ID, and no override distinct_id is passed, this function will do nothing. Context tags are folded into $set properties, so tagging the current context and then calling `set` will cause those tags to be set on the user (unlike capture, which causes them to just be set on the event). - -
- -**Examples:** - -```python -# Set person properties -from posthog import set -set(distinct_id='distinct_id', properties={'name': 'Max Hedgehog'}) -``` - -### `posthog.set_once` - -```python -set_once(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str] -``` - -Set properties on a user record, only if they do not yet exist. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`**kwargs` | `Unpack[OptionalSetArgs]` | Optional arguments including: distinct_id: Unique identifier for the user. Falls back to the context distinct ID; if none exists, this call does nothing. properties: Dict of person properties to set only once. timestamp: When the properties were set. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. uuid: Unique identifier for this operation. If omitted, one is generated and returned. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. disable_geoip: Whether to disable GeoIP lookup. | `{}` - -
-Details - -This will not overwrite previous people property values, unlike `set`. Otherwise, operates in an identical manner to `set`. - -
- -**Examples:** - -```python -# Set property once -from posthog import set_once -set_once(distinct_id='distinct_id', properties={'initial_url': '/blog'}) - -``` - -### `posthog.group_identify` - -```python -group_identify(group_type: str, group_key: str, properties: Optional[Dict[str, Any]] = None, timestamp: Optional[Union[datetime.datetime, str]] = None, uuid: Optional[str] = None, disable_geoip: Optional[bool] = None, distinct_id: Optional[ID_TYPES] = None, options: Optional[Dict[str, Any]] = None) -> Optional[str] -``` - -Set properties on a group. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`group_type` | `str` | Type of your group. Required - the call is dropped with a warning if it is missing or empty. | *required* -`group_key` | `str` | Unique identifier of the group. Required - the call is dropped with a warning if it is missing or empty. | *required* -`properties` | `Optional[Dict[str, Any]]` | Properties to set on the group | `None` -`timestamp` | `Optional[Union[datetime, str]]` | Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | `None` -`uuid` | `Optional[str]` | Optional UUID for the event | `None` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup | `None` -`distinct_id` | `Optional[ID_TYPES]` | Optional distinct ID of the user performing the action | `None` -`options` | `Optional[Dict[str, Any]]` | Optional capture options for the event, sent as given | `None` - -**Examples:** - -```python -# Group identify -from posthog import group_identify -group_identify('company', 'company_id_in_your_db', { - 'name': 'Awesome Inc.', - 'employees': 11 -}) -``` - -### `posthog.alias` - -```python -alias(previous_id: ID_TYPES, distinct_id: str, timestamp: Optional[Union[datetime.datetime, str]] = None, uuid: Optional[str] = None, disable_geoip: Optional[bool] = None, options: Optional[Dict[str, Any]] = None) -> Optional[str] -``` - -Associate user behaviour before and after they e.g. register, login, or perform some other identifying action. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`previous_id` | `ID_TYPES` | The unique ID of the user before | *required* -`distinct_id` | `str` | The current unique id | *required* -`timestamp` | `Optional[Union[datetime, str]]` | Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | `None` -`uuid` | `Optional[str]` | Optional UUID for the event | `None` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup | `None` -`options` | `Optional[Dict[str, Any]]` | Optional capture options for the event, sent as given | `None` - -
-Details - -To marry up whatever a user does before they sign up or log in with what they do after you need to make an alias call. This will allow you to answer questions like "Which marketing channels leads to users churning after a month?" or "What do users do on our website before signing up?". Particularly useful for associating user behaviour before and after they e.g. register, login, or perform some other identifying action. - -
- -**Examples:** - -```python -# Alias user -from posthog import alias -alias(previous_id='distinct_id', distinct_id='alias_id') -``` - -### `posthog.capture_exception` - -```python -capture_exception(exception: Optional[ExceptionArg] = None, *, level: Optional[str] = None, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] -``` - -Capture exceptions that happen in your code. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`exception` | `Optional[ExceptionArg]` | The exception to capture. If not provided, the current exception is captured via `sys.exc_info()` | `None` -`level` | `Optional[str]` | The `$exception_level`, such as `"warning"` or `"fatal"`. Defaults to `"error"`. An unknown value counts as unset. | `None` -`**kwargs` | `Unpack[OptionalCaptureArgs]` | Optional capture arguments including distinct_id, properties, timestamp, uuid, groups, flags, send_feature_flags, disable_geoip, and options. Reserved exception properties in `properties`, such as `$exception_list` and `$exception_level`, are ignored. | `{}` - -
-Details - -Capture exception is idempotent - if it is called twice with the same exception instance, only a occurrence will be tracked in posthog. This is because, generally, contexts will cause exceptions to be captured automatically. However, to ensure you track an exception, if you catch and do not re-raise it, capturing it manually is recommended, unless you are certain it will have crossed a context boundary (e.g. by existing a `with posthog.new_context():` block already). If the passed exception was raised and caught, the captured stack trace will consist of every frame between where the exception was raised and the point at which it is captured (the "traceback"). If the passed exception was never raised, e.g. if you call `posthog.capture_exception(ValueError("Some Error"))`, the stack trace captured will be the full stack trace at the moment the exception was captured. Note that heavy use of contexts will lead to truncated stack traces, as the exception will be captured by the context entered most recently, which may not be the point you catch the exception for the final time in your code. It's recommended to use contexts sparingly, for this reason. `capture_exception` takes the same set of optional arguments as `capture`. - -
- -**Examples:** - -```python -# Capture exception -from posthog import capture_exception -try: - risky_operation() -except Exception as e: - capture_exception(e) -``` - -### `posthog.feature_enabled` - -```python -feature_enabled(key: str, distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[bool] -``` - -Use feature flags to enable or disable features for users. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The feature flag key | *required* -`distinct_id` | `ID_TYPES` | The user's distinct ID | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | Groups mapping | `None` -`person_properties` | `Optional[Dict[str, Any]]` | Person properties | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties | `None` -`only_evaluate_locally` | `bool` | Whether to evaluate only locally | `False` -`send_feature_flag_events` | `bool` | Whether to send feature flag events | `True` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup | `None` -`device_id` | `Optional[str]` | Optional device ID override for experience-continuity flags | `None` - -
-Details - -You can call `posthog.load_feature_flags()` before to make sure you're not doing unexpected requests. - -
- -**Examples:** - -```python -# Boolean feature flag -from posthog import feature_enabled, get_feature_flag_payload -is_my_flag_enabled = feature_enabled('flag-key', 'distinct_id_of_your_user') -if is_my_flag_enabled: - matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user') -``` - -### `posthog.get_feature_flag` - -```python -get_feature_flag(key: str, distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[FlagValue] -``` - -Get feature flag variant for users. Used with experiments. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The feature flag key | *required* -`distinct_id` | `ID_TYPES` | The user's distinct ID | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | Groups mapping from group type to group key | `None` -`person_properties` | `Optional[Dict[str, Any]]` | Person properties | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties in format { group_type_name: { group_properties } } | `None` -`only_evaluate_locally` | `bool` | Whether to evaluate only locally | `False` -`send_feature_flag_events` | `bool` | Whether to send feature flag events | `True` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup | `None` -`device_id` | `Optional[str]` | Optional device ID override for experience-continuity flags | `None` - -
-Details - -`groups` are a mapping from group type to group key. So, if you have a group type of "organization" and a group key of "5", you would pass groups={"organization": "5"}. `group_properties` take the format: { group_type_name: { group_properties } }. So, for example, if you have the group type "organization" and the group key "5", with the properties name, and employee count, you'll send these as: group_properties={"organization": {"name": "PostHog", "employees": 11}}. - -
- -**Examples:** - -```python -# Multivariate feature flag -from posthog import get_feature_flag, get_feature_flag_payload -enabled_variant = get_feature_flag('flag-key', 'distinct_id_of_your_user') -if enabled_variant == 'variant-key': - matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user') -``` - -### `posthog.get_all_flags` - -```python -get_all_flags(distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None, flag_keys_to_evaluate: Optional[list[str]] = None) -> Optional[dict[str, FlagValue]] -``` - -Get all flags for a given user. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | `ID_TYPES` | The user's distinct ID | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | Groups mapping | `None` -`person_properties` | `Optional[Dict[str, Any]]` | Person properties | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties | `None` -`only_evaluate_locally` | `bool` | Whether to evaluate only locally | `False` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup | `None` -`device_id` | `Optional[str]` | Optional device ID override for experience-continuity flags | `None` -`flag_keys_to_evaluate` | `Optional[list[str]]` | Optional list of flag keys to evaluate (evaluates all if None) | `None` - -
-Details - -Flags are key-value pairs where the key is the flag key and the value is the flag variant, or True, or False. - -
- -**Examples:** - -```python -# All flags for user -from posthog import get_all_flags -get_all_flags('distinct_id_of_your_user') -``` - -### `posthog.get_feature_flag_result` - -```python -get_feature_flag_result(key: str, distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[FeatureFlagResult] -``` - -Get a FeatureFlagResult object which contains the flag result and payload. - -This method evaluates a feature flag and returns a FeatureFlagResult object containing: -- enabled: Whether the flag is enabled -- variant: The variant value if the flag has variants -- payload: The payload associated with the flag (automatically deserialized from JSON) -- key: The flag key -- reason: Why the flag was enabled/disabled - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The feature flag key. | *required* -`distinct_id` | `ID_TYPES` | The user's distinct ID. | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | Mapping of group type to group key. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | Person properties to use for evaluation. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties keyed by group type. | `None` -`only_evaluate_locally` | `bool` | Whether to evaluate only locally. | `False` -`send_feature_flag_events` | `bool` | Whether to send a $feature_flag_called event. | `True` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup. | `None` -`device_id` | `Optional[str]` | Optional device ID override for experience-continuity flags. | `None` - -Example: -```python -result = posthog.get_feature_flag_result('beta-feature', 'distinct_id') -if result and result.enabled: - # Use the variant and payload - print(f"Variant: {result.variant}") - print(f"Payload: {result.payload}") -``` - -### `posthog.get_feature_flag_payload` - -```python -get_feature_flag_payload(key: str, distinct_id: ID_TYPES, match_value: Optional[FlagValue] = None, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[object] -``` - -Get the payload associated with a feature flag value. - -Deprecated for new code. Prefer `evaluate_flags()` and -`flags.get_flag_payload(key)` so flag evaluation happens once per request. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The feature flag key. | *required* -`distinct_id` | `ID_TYPES` | The user's distinct ID. | *required* -`match_value` | `Optional[FlagValue]` | Optional flag value to use when selecting a payload. | `None` -`groups` | `Optional[Mapping[str, Union[str, int]]]` | Mapping of group type to group key. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | Person properties to use for evaluation. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties keyed by group type. | `None` -`only_evaluate_locally` | `bool` | Whether to evaluate only locally. | `False` -`send_feature_flag_events` | `bool` | Whether to send a $feature_flag_called event. | `True` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup. | `None` -`device_id` | `Optional[str]` | Optional device ID override for experience-continuity flags. | `None` - -**Returns:** - -Type | Description ----- | ----------- -`Optional[object]` | The payload associated with the matched feature flag value, or None. -`Optional[object]` | This function returns the payload only, not the FeatureFlagResult wrapper -`Optional[object]` | used internally to compute it. - -### `posthog.get_remote_config_payload` - -```python -get_remote_config_payload(key: str) -``` - -Get the payload for a remote config feature flag. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The key of the feature flag | *required* - -**Returns:** - -Type | Description ----- | ----------- - | The payload associated with the feature flag. If payload is encrypted, the return value will be decrypted - -
-Note - -Requires secret_key to be set for authentication - -
- -### `posthog.get_all_flags_and_payloads` - -```python -get_all_flags_and_payloads(distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None, flag_keys_to_evaluate: Optional[list[str]] = None) -> FlagsAndPayloads -``` - -Get all feature flag values and payloads for a user. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | `ID_TYPES` | The user's distinct ID. | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | Mapping of group type to group key. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | Person properties to use for evaluation. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties keyed by group type. | `None` -`only_evaluate_locally` | `bool` | Whether to evaluate only locally. | `False` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup. | `None` -`device_id` | `Optional[str]` | Optional device ID override for experience-continuity flags. | `None` -`flag_keys_to_evaluate` | `Optional[list[str]]` | Optional list of flag keys to evaluate. Evaluates all flags when omitted. | `None` - -**Returns:** - -Type | Description ----- | ----------- -`FlagsAndPayloads` | A dict with `featureFlags` and `featureFlagPayloads` entries. - -### `posthog.evaluate_flags` - -```python -evaluate_flags(distinct_id: Optional[ID_TYPES] = None, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, flag_keys: Optional[list[str]] = None, device_id: Optional[str] = None) -> FeatureFlagEvaluations -``` - -Evaluate all feature flags for a user in a single call and return a -`FeatureFlagEvaluations` snapshot. Branch on `.is_enabled()` / -`.get_flag()` and pass the same snapshot to `capture()` via the -`flags` option so events carry the exact flag values the code branched on. - -Prefer this over repeated `get_feature_flag()` calls and over -`capture(send_feature_flags=True)` — it consolidates flag evaluation into -a single `/flags` request per incoming request. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | `Optional[ID_TYPES]` | The user's distinct ID. If `None`, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot. | `None` -`groups` | `Optional[Mapping[str, Union[str, int]]]` | Mapping of group type to group key. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | Person properties to use for evaluation. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties keyed by group type. | `None` -`only_evaluate_locally` | `bool` | If `True`, never fall back to remote evaluation and omit flags that cannot be evaluated locally. The snapshot's `unresolved_flags` lists them with a reason. | `False` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup. | `None` -`flag_keys` | `Optional[list[str]]` | Optional list that scopes local evaluation, the underlying `/flags` request, and the returned snapshot. When omitted or `None`, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per `evaluate_flags` call unless `only_evaluate_locally` is `True`. If the server also does not know the key, it is omitted from the snapshot. | `None` -`device_id` | `Optional[str]` | Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes. | `None` - -**Examples:** - -```python -from posthog import evaluate_flags, capture -flags = evaluate_flags("user_123", person_properties={"plan": "enterprise"}) -if flags.is_enabled("new-dashboard"): - render_new_dashboard() -capture("page_viewed", distinct_id="user_123", flags=flags) -``` - -### `posthog.feature_flag_definitions` - -```python -feature_flag_definitions() -``` - -Returns loaded feature flags. - -
-Details - -Returns loaded feature flags, if any. Helpful for debugging what flag information you have loaded. - -
- -**Examples:** - -```python -from posthog import feature_flag_definitions -definitions = feature_flag_definitions() -``` - -### `posthog.get_feature_flag_evaluation_runtime` - -```python -get_feature_flag_evaluation_runtime(key: str) -> Optional[FeatureFlagEvaluationRuntime] -``` - -Return where a locally loaded feature flag is meant to be evaluated. - -
-Details - -Reads the `evaluation_runtime` each flag definition carries, so no extra -request is made. Returns `None` when local evaluation has not loaded a -definition for this key. A definition that carries no runtime reports -`FeatureFlagEvaluationRuntime.ALL`, the default PostHog applies. - -
- -**Examples:** - -```python -from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_evaluation_runtime -runtime = get_feature_flag_evaluation_runtime("my-flag") -``` - -
-Category - -Feature flags - -
- -### `posthog.get_feature_flag_keys_by_evaluation_runtime` - -```python -get_feature_flag_keys_by_evaluation_runtime(evaluation_runtime: Union[FeatureFlagEvaluationRuntime, str]) -> list[str] -``` - -Return the keys of locally loaded flags that a runtime can evaluate. - -
-Details - -A flag set to `FeatureFlagEvaluationRuntime.ALL` suits either runtime, so -it is returned for `CLIENT` and for `SERVER`. Use this to decide which -flags to hand to a browser when a backend serves flags to its own -frontend. - -
- -**Examples:** - -```python -from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_keys_by_evaluation_runtime -client_keys = get_feature_flag_keys_by_evaluation_runtime(FeatureFlagEvaluationRuntime.CLIENT) -``` - -
-Category - -Feature flags - -
- -### `posthog.load_feature_flags` - -```python -load_feature_flags() -``` - -Load feature flag definitions from PostHog. - -**Examples:** - -```python -from posthog import load_feature_flags -load_feature_flags() -``` - -### `posthog.flush` - -```python -flush(timeout_seconds: Optional[float] = 10) -> None -``` - -Attempt immediate delivery of queued events and ended spans, keeping the global client reusable. - -Call `flush()` before a short-lived request or serverless invocation returns -when using the shared module-level client. Use `shutdown()` only for final -process cleanup or a client that will not be reused. Flush does not stop the -background workers or feature flag poller, and does not end open spans. - -Returning does not guarantee server receipt: pending work can remain after a -timeout or span export failure, and failed events may be dropped by consumers. -Calls made directly from SDK callbacks such as `on_error` are deferred -rather than blocking the worker that invoked the callback. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`timeout_seconds` | `Optional[float]` | Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass `None` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently, waiting up to this many seconds for an in-flight flush before starting a fresh drain budget. A lock wait timeout leaves the in-flight flush to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. The last span request is not cut short and is bounded by the client's `timeout` in seconds, so this is not a strict wall-clock limit for the whole call. With `None`, a retriable span failure is left for a later flush rather than retried within this call. | `10` - -**Examples:** - -```python -import posthog -posthog.capture('event_name', distinct_id='user_id') -# Before a serverless invocation returns; keep the global client open. -posthog.flush(timeout_seconds=10) -``` - -### `posthog.join` - -```python -join() -> None -``` - -Attempt to process queued events and stop the client's background workers. Use `shutdown()` directly in most cases. - -Failed or undrainable events may be dropped and reported through logging or -`on_error`; returning does not guarantee server receipt. Lifecycle cleanup -is attempted once, and cleanup failures are logged without retry. - -**Examples:** - -```python -from posthog import join -join() -``` - -### `posthog.start_span` - -```python -start_span(name: str, *, kind: Optional[str] = None, attributes: Optional[Mapping[str, Any]] = None, parent: Union[Span, str, None] = None, tracestate: Optional[str] = None, start_time: Union[datetime.datetime, float, None] = None) -> Span -``` - -Start a span for distributed tracing. Alpha. - -Returns a span handle. Use it as a context manager to make it the active -span for the block and end it on exit (recording a raised exception on the -way out); or call `end()` yourself for a span that cannot wrap a block. -Spans started inside the block nest under it automatically. Always returns -a usable handle, even when tracing is off, so calling code never branches. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`name` | `str` | A low-cardinality operation name, e.g. `GET /users/:id`. Variable values belong in attributes, not the name. | *required* -`kind` | `Optional[str]` | `internal` (default), `server`, `client`, `producer` or `consumer`. | `None` -`attributes` | `Optional[Mapping[str, Any]]` | Initial attributes. | `None` -`parent` | `Union[Span, str, None]` | A span handle, or an inbound W3C `traceparent` header value to continue a remote trace. Defaults to the active span. | `None` -`tracestate` | `Optional[str]` | The inbound `tracestate` header accompanying a `traceparent` string `parent`; preserved and propagated. | `None` -`start_time` | `Union[datetime, float, None]` | A `datetime` or epoch seconds, to backdate the span. | `None` - -**Examples:** - -```python -import posthog -posthog.traces = {"service_name": "checkout-api"} - -with posthog.start_span("POST /checkout", parent=request.headers.get("traceparent")) as span: - span.set_attribute("plan", user.plan) - with posthog.start_span("db.query", kind="client"): - ... - outgoing_headers = {"traceparent": span.traceparent()} -``` - -### `posthog.get_active_span` - -```python -get_active_span() -> Optional[Span] -``` - -The span that is active in the current context, or `None`. Alpha. - -Only entering a span (`with posthog.start_span(...) as span:`) makes it -active; a span started manually is not. Use it to propagate the trace to -the next service: `span.traceparent()` is the header value. - -**Examples:** - -```python -span = posthog.get_active_span() -if span is not None: - headers["traceparent"] = span.traceparent() -``` - -### `posthog.shutdown` - -```python -shutdown() -> None -``` - -Attempt final delivery and permanently shut down the global client. - -Call this for final process cleanup, not at the end of each request or -serverless invocation that shares the module-level client. Use `flush()` -before returning from those invocations instead. Shutdown closes capture -lanes, stops background workers and the feature flag poller, and tears down -integrations. The global client is not recreated automatically after shutdown. - -This normally blocks until queued events have been attempted and cleanup -finishes. Failed or undrainable events may be dropped and reported through -logging or `on_error`; returning does not guarantee server receipt. Event -draining has no timeout. Queued spans get a final flush with up to 30 seconds -waiting for an in-flight flush, then a fresh 30-second drain budget; the last -HTTP request is not cut short. Unsent queued spans and still-open spans are -discarded with a warning. -Lifecycle cleanup is attempted once, and cleanup failures are logged without -retry. Calls made directly from SDK callbacks such as `on_error` are deferred -to avoid deadlocking the worker. If blocking completion is required, signal an application-owned -thread, return from the callback, and call `shutdown()` from that thread. -Do not wait inside a callback for another thread or task calling a lifecycle -method. - -**Examples:** - -```python -from posthog import shutdown -shutdown() -``` - -### `posthog.setup` - -```python -setup() -> Client -``` - -Create or return the global PostHog client configured by module settings. - -Most applications should either instantiate `Posthog` directly or set -`posthog.api_key`/other module settings before calling top-level helpers. -`setup()` is called automatically by global APIs such as `capture()`. - -**Returns:** - -Type | Description ----- | ----------- -`Client` | The global `Client` instance. If both `api_key` and -`Client` | `project_api_key` are missing or blank, the client is disabled and -`Client` | module-level calls become no-ops. - -## Types - -### `OptionalCaptureArgs` - -Bases: `TypedDict` - -Optional arguments for the capture method. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | | Unique identifier for the person associated with this event. If not set, the context distinct_id is used, if available, otherwise a UUID is generated, and the event is marked as personless. Setting context-level distinct_id's is recommended. | -`properties` | | Dictionary of properties to track with the event | -`timestamp` | | When the event occurred (defaults to current time). UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | -`uuid` | | Unique identifier for this specific event. If not provided, one is generated. The event UUID is returned, so you can correlate it with actions in your app (like showing users an error ID if you capture an exception). If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | -`groups` | | Group identifiers to associate with this event (format: {group_type: group_key}) | -`flags` | | A `FeatureFlagEvaluations` snapshot from `evaluate_flags()`. The exact flag values from the snapshot are attached to the event with no additional network call — prefer this over `send_feature_flags`. | -`send_feature_flags` | | Deprecated — prefer `flags` with a `FeatureFlagEvaluations` snapshot. Whether to include currently active feature flags in the event properties. Can be a boolean or a SendFeatureFlagsOptions object. Defaults to False. Fires a hidden `/flags` request on capture and may return different values than the ones the code branched on. | -`disable_geoip` | | Whether to disable GeoIP lookup for this event. Defaults to False. | -`options` | | Capture options for this event, such as `{"process_person_profile": False}`. Sent as given, for PostHog to validate. An option wins over its legacy `$` property, such as `$process_person_profile`. A value that is not a dict is logged and ignored. | - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`disable_geoip` | `NotRequired[Optional[bool]]` | -`distinct_id` | `NotRequired[Optional[ID_TYPES]]` | -`flags` | `NotRequired[Optional[FeatureFlagEvaluations]]` | -`groups` | `NotRequired[Optional[Dict[str, str]]]` | -`options` | `NotRequired[Optional[Dict[str, Any]]]` | -`properties` | `NotRequired[Optional[Dict[str, Any]]]` | -`send_feature_flags` | `NotRequired[Optional[Union[bool, SendFeatureFlagsOptions]]]` | -`timestamp` | `NotRequired[Optional[Union[datetime, str]]]` | -`uuid` | `NotRequired[Optional[Union[str, UUID]]]` | - -#### `posthog.args.OptionalCaptureArgs.disable_geoip` - -```python -disable_geoip: NotRequired[Optional[bool]] -``` - -#### `posthog.args.OptionalCaptureArgs.distinct_id` - -```python -distinct_id: NotRequired[Optional[ID_TYPES]] -``` - -#### `posthog.args.OptionalCaptureArgs.flags` - -```python -flags: NotRequired[Optional[FeatureFlagEvaluations]] -``` - -#### `posthog.args.OptionalCaptureArgs.groups` - -```python -groups: NotRequired[Optional[Dict[str, str]]] -``` - -#### `posthog.args.OptionalCaptureArgs.options` - -```python -options: NotRequired[Optional[Dict[str, Any]]] -``` - -#### `posthog.args.OptionalCaptureArgs.properties` - -```python -properties: NotRequired[Optional[Dict[str, Any]]] -``` - -#### `posthog.args.OptionalCaptureArgs.send_feature_flags` - -```python -send_feature_flags: NotRequired[Optional[Union[bool, SendFeatureFlagsOptions]]] -``` - -#### `posthog.args.OptionalCaptureArgs.timestamp` - -```python -timestamp: NotRequired[Optional[Union[datetime, str]]] -``` - -#### `posthog.args.OptionalCaptureArgs.uuid` - -```python -uuid: NotRequired[Optional[Union[str, UUID]]] -``` - -### `FeatureFlagEvaluations` - -```python -FeatureFlagEvaluations(host: _FeatureFlagEvaluationsHost, distinct_id: str, flags: Dict[str, _EvaluatedFlagRecord], groups: Optional[Mapping[str, Union[str, int]]] = None, disable_geoip: Optional[bool] = None, request_id: Optional[str] = None, evaluated_at: Optional[int] = None, errors_while_computing: bool = False, quota_limited: bool = False, minimal_flag_called_events: bool = False, accessed: Optional[Set[str]] = None, unresolved_flags: Optional[Mapping[str, UnresolvedFlagReason]] = None) -> None -``` - -A point-in-time snapshot of feature flag evaluations for a single distinct_id. - -Returned by `Client.evaluate_flags` — branch on `is_enabled` / -`get_flag` and pass the same object to `Client.capture` via the -`flags` option so the captured event carries the exact flag values the code -branched on. - -Example:: - - flags = posthog.evaluate_flags(distinct_id, person_properties={"plan": "enterprise"}) - if flags.is_enabled("new-dashboard"): - render_new_dashboard() - posthog.capture("page_viewed", distinct_id=distinct_id, flags=flags) - -To narrow the set of flags that get attached to a captured event, use the in-memory -helpers `only` and `only_accessed`. To narrow the set of flags requested -from the server in the first place, pass `flag_keys` to `Client.evaluate_flags`. - -**Functions:** - -Name | Description ----- | ----------- -`get_flag` | Return the flag value. Fires `$feature_flag_called` on first access. -`get_flag_payload` | Return the payload associated with a flag. -`is_enabled` | Return whether the flag is enabled. Fires `$feature_flag_called` on the -`only` | Return a filtered copy containing only flags with the given keys. Keys that -`only_accessed` | Return a filtered copy containing only flags accessed via `is_enabled` - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`keys` | `List[str]` | Return the flag keys that are part of this evaluation. -`unresolved_flags` | `Mapping[str, UnresolvedFlagReason]` | Return a read-only mapping of flag key to the reason local evaluation - -Internal — instances are created by the SDK via `Client.evaluate_flags()`. - -#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.get_flag` - -```python -get_flag(key: str) -> Optional[FlagValue] -``` - -Return the flag value. Fires `$feature_flag_called` on first access. - -Returns the variant string for multivariate flags, `True` for enabled flags -without a variant, `False` for disabled flags, and `None` for flags that -were not returned by the evaluation. - -#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.get_flag_payload` - -```python -get_flag_payload(key: str) -> Optional[Any] -``` - -Return the payload associated with a flag. - -Does not count as an access for `only_accessed` and does not fire any event. - -#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.is_enabled` - -```python -is_enabled(key: str, default_value: bool = False) -> bool -``` - -Return whether the flag is enabled. Fires `$feature_flag_called` on the -first access per (distinct_id, flag, value) tuple, deduped via the SDK's cache. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The feature flag key. | *required* -`default_value` | `bool` | Returned when the flag has no value in this evaluation — the key was not part of the evaluated set, or the evaluation itself came back empty (failed `/flags` request, quota limit, or no resolvable `distinct_id`). Defaults to `False`. | `False` - -A flag that has a value always wins over `default_value`, including a -disabled flag, which returns `False` even when `default_value=True`. - -#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.keys` - -```python -keys: List[str] -``` - -Return the flag keys that are part of this evaluation. - -#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.only` - -```python -only(keys: List[str]) -> FeatureFlagEvaluations -``` - -Return a filtered copy containing only flags with the given keys. Keys that -are not present in the evaluation are dropped and logged as a warning. - -#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.only_accessed` - -```python -only_accessed() -> FeatureFlagEvaluations -``` - -Return a filtered copy containing only flags accessed via `is_enabled` -or `get_flag` before this call. - -Order-dependent: if nothing has been accessed yet, the returned snapshot is -empty. The method honors its name — pre-access if you want a populated result. - -#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.unresolved_flags` - -```python -unresolved_flags: Mapping[str, UnresolvedFlagReason] -``` - -Return a read-only mapping of flag key to the reason local evaluation -left it without a value. - -A flag is listed when it has a loaded local definition, was in the requested -scope, was inconclusive locally, and no remote value filled the gap. Listed -flags are not part of `keys`, and reading one reports the -`local_evaluation_inconclusive` error instead of `flag_missing`. Reading -this mapping does not count as an access and does not fire any event. -Snapshots returned by `only` and `only_accessed`, and by -`AsyncClient.evaluate_flags()`, which evaluates only remotely, carry no -entries. - -### `FeatureFlagResult` - -```python -FeatureFlagResult(key: str, enabled: bool, variant: Optional[str], payload: Optional[Any], reason: Optional[str]) -> None -``` - -The result of calling a feature flag which includes the flag result, variant, and payload. - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`key` | `str` | The unique identifier of the feature flag. -`enabled` | `bool` | Whether the feature flag is enabled for the current context. -`variant` | `Optional[str]` | The variant value if the flag is enabled and has variants, None otherwise. -`payload` | `Optional[Any]` | Additional data associated with the feature flag, if any. -`reason` | `Optional[str]` | A description of why the flag was enabled or disabled, if available. - -**Functions:** - -Name | Description ----- | ----------- -`from_flag_details` | Create a FeatureFlagResult from a FeatureFlag object. -`from_value_and_payload` | Creates a FeatureFlagResult from a flag value and payload. -`get_value` | Returns the value of the flag. This is the variant if it exists, otherwise the enabled value. - -#### `posthog.types.FeatureFlagResult.enabled` - -```python -enabled: bool -``` - -#### `posthog.types.FeatureFlagResult.from_flag_details` - -```python -from_flag_details(details: Union[FeatureFlag, None], override_match_value: Optional[FlagValue] = None) -> FeatureFlagResult | None -``` - -Create a FeatureFlagResult from a FeatureFlag object. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`details` | `Union[FeatureFlag, None]` | The FeatureFlag object to convert. | *required* -`override_match_value` | `Optional[FlagValue]` | If provided, this value will be used to populate the enabled and variant fields instead of the values from the FeatureFlag. | `None` - -**Returns:** - -Type | Description ----- | ----------- -`FeatureFlagResult \| None` | FeatureFlagResult | None: A new FeatureFlagResult instance, or None if details is None. - -#### `posthog.types.FeatureFlagResult.from_value_and_payload` - -```python -from_value_and_payload(key: str, value: Union[FlagValue, None], payload: Any) -> Union[FeatureFlagResult, None] -``` - -Creates a FeatureFlagResult from a flag value and payload. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The unique identifier of the feature flag. | *required* -`value` | `Union[FlagValue, None]` | The value of the flag (string variant or boolean). | *required* -`payload` | `Any` | Additional data associated with the feature flag. | *required* - -**Returns:** - -Type | Description ----- | ----------- -`Union[FeatureFlagResult, None]` | Union[FeatureFlagResult, None]: A new FeatureFlagResult instance, or None if value is None. - -#### `posthog.types.FeatureFlagResult.get_value` - -```python -get_value() -> FlagValue -``` - -Returns the value of the flag. This is the variant if it exists, otherwise the enabled value. -This is the value we report as `$feature_flag_response` in the `$feature_flag_called` event. - -**Returns:** - -Name | Type | Description ----- | ---- | ----------- -`FlagValue` | `FlagValue` | Either a string variant or boolean value representing the flag's state. - -#### `posthog.types.FeatureFlagResult.key` - -```python -key: str -``` - -#### `posthog.types.FeatureFlagResult.payload` - -```python -payload: Optional[Any] -``` - -#### `posthog.types.FeatureFlagResult.reason` - -```python -reason: Optional[str] -``` - -#### `posthog.types.FeatureFlagResult.variant` - -```python -variant: Optional[str] -``` - -### `FeatureFlag` - -```python -FeatureFlag(key: str, enabled: bool, variant: Optional[str], reason: Optional[FlagReason], metadata: Union[FlagMetadata, LegacyFlagMetadata]) -> None -``` - -Detailed feature flag evaluation returned by the flags API. - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`key` | `str` | Feature flag key. -`enabled` | `bool` | Whether the flag is enabled for the evaluated user or group. -`variant` | `Optional[str]` | Variant key for multivariate flags, otherwise `None`. -`reason` | `Optional[FlagReason]` | Optional reason metadata explaining the result. -`metadata` | `Union[FlagMetadata, LegacyFlagMetadata]` | Payload and other metadata returned by the API. - -**Functions:** - -Name | Description ----- | ----------- -`from_json` | Build a flag from one entry of a flags API response. Missing `metadata` becomes empty legacy metadata. -`from_value_and_payload` | Build a flag from its value and payload. A string value is a variant, so the flag is enabled. -`get_value` | Return the variant for a multivariate flag, otherwise whether the flag is enabled. - -#### `posthog.types.FeatureFlag.enabled` - -```python -enabled: bool -``` - -#### `posthog.types.FeatureFlag.from_json` - -```python -from_json(resp: Any) -> FeatureFlag -``` - -Build a flag from one entry of a flags API response. Missing `metadata` becomes empty legacy metadata. - -#### `posthog.types.FeatureFlag.from_value_and_payload` - -```python -from_value_and_payload(key: str, value: FlagValue, payload: Any) -> FeatureFlag -``` - -Build a flag from its value and payload. A string value is a variant, so the flag is enabled. - -#### `posthog.types.FeatureFlag.get_value` - -```python -get_value() -> FlagValue -``` - -Return the variant for a multivariate flag, otherwise whether the flag is enabled. - -#### `posthog.types.FeatureFlag.key` - -```python -key: str -``` - -#### `posthog.types.FeatureFlag.metadata` - -```python -metadata: Union[FlagMetadata, LegacyFlagMetadata] -``` - -#### `posthog.types.FeatureFlag.reason` - -```python -reason: Optional[FlagReason] -``` - -#### `posthog.types.FeatureFlag.variant` - -```python -variant: Optional[str] -``` - -### `FlagsAndPayloads` - -Bases: `TypedDict` - -Feature flag values and payloads keyed by feature flag key. - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`featureFlags` | `Optional[dict[str, FlagValue]]` | Each flag's value, keyed by flag key. -`featureFlagPayloads` | `Optional[dict[str, Any]]` | Each flag's payload, keyed by flag key. - -#### `posthog.types.FlagsAndPayloads.featureFlagPayloads` - -```python -featureFlagPayloads: Optional[dict[str, Any]] -``` - -#### `posthog.types.FlagsAndPayloads.featureFlags` - -```python -featureFlags: Optional[dict[str, FlagValue]] -``` - -### `UnresolvedFlagReason` - -Bases: `str`, `Enum` - -Why local evaluation left a flag without a value. - -Reported by `FeatureFlagEvaluations.unresolved_flags`. Inheriting from -`str` keeps the members directly comparable to their string values. When more -than one cause applies, the first reason in the order below is reported. - -
-Values - -EXPERIENCE_CONTINUITY: The flag has experience continuity enabled, which - local evaluation never resolves. Change the flag to fix it. -UNSUPPORTED_DEFINITION: The flag uses something the local evaluator does not - support, such as a static cohort, an unknown operator, a malformed value, - a dependency with no loaded definition or a circular dependency. Change - the flag or upgrade the SDK to fix it. -UNRESOLVED_DEPENDENCY: A flag this flag depends on has a loaded definition - but was unresolved. -MISSING_CONTEXT: The call did not pass a property, group property or device - id the flag needs, or passed it in an unusable form. Pass it to fix it. - -
- -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`EXPERIENCE_CONTINUITY` | | -`MISSING_CONTEXT` | | -`UNRESOLVED_DEPENDENCY` | | -`UNSUPPORTED_DEFINITION` | | - -#### `posthog.types.UnresolvedFlagReason.EXPERIENCE_CONTINUITY` - -```python -EXPERIENCE_CONTINUITY = 'experience_continuity' -``` - -#### `posthog.types.UnresolvedFlagReason.MISSING_CONTEXT` - -```python -MISSING_CONTEXT = 'missing_context' -``` - -#### `posthog.types.UnresolvedFlagReason.UNRESOLVED_DEPENDENCY` - -```python -UNRESOLVED_DEPENDENCY = 'unresolved_dependency' -``` - -#### `posthog.types.UnresolvedFlagReason.UNSUPPORTED_DEFINITION` - -```python -UNSUPPORTED_DEFINITION = 'unsupported_definition' -``` - -### `InconclusiveMatchError` - -```python -InconclusiveMatchError(*args, reason: Optional[UnresolvedFlagReason] = None) -``` - -Bases: `Exception` - -Raised when local evaluation can't determine a flag's value. `reason` says -why, as reported by `FeatureFlagEvaluations.unresolved_flags`. - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`reason` | `UnresolvedFlagReason` | Why local evaluation couldn't resolve the flag. - -#### `posthog.feature_flags.InconclusiveMatchError.reason` - -```python -reason: UnresolvedFlagReason = UnresolvedFlagReason.UNSUPPORTED_DEFINITION -``` - -### `RequiresServerEvaluation` - -```python -RequiresServerEvaluation(*args, reason: Optional[UnresolvedFlagReason] = None) -``` - -Bases: `Exception` - -Raised when feature flag evaluation requires server-side data that is not -available locally (e.g., static cohorts). - -This error should propagate immediately to trigger API fallback, unlike -InconclusiveMatchError which allows trying other conditions. - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`reason` | `UnresolvedFlagReason` | Why the flag needs server-side evaluation. - -#### `posthog.feature_flags.RequiresServerEvaluation.reason` - -```python -reason: UnresolvedFlagReason = UnresolvedFlagReason.UNSUPPORTED_DEFINITION -``` - -### `CaptureError` - -```python -CaptureError(status: int | str, message: str, *, endpoint: str, retry_after: Optional[float] = None, request_id: Optional[str] = None, attempts: Optional[int] = None, retry_exhausted: Optional[list[str]] = None, drops: Optional[list[tuple[str, Optional[str]]]] = None, event_results: Optional[dict[str, CaptureEventResult]] = None) -``` - -Bases: `APIError` - -A capture batch that was not fully delivered. - -Passed to `on_error` with the batch. `status` is the HTTP status of the -last attempt, or `0` when no response arrived (`__cause__` holds the -transport error). A 2xx `status` means the request succeeded but some -events were dropped or ran out of retries. - -`event_results` maps each event uuid to its last verdict from a 2xx -response. Events missing from it never got a verdict, because every -attempt that reached them failed as a whole request. - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`status` | | The HTTP status of the last attempt, or `0` when no response arrived. -`message` | | What went wrong. -`endpoint` | | The capture path the batch was sent to, so one `on_error` handler can tell the analytics and AI lanes apart. -`retry_after` | | Seconds the last response's `Retry-After` header asked to wait, or `None` when it sent none. -`request_id` | | The ID the SDK gave the batch. Every attempt sends the same ID. -`attempts` | | How many times the SDK sent the batch. -`retry_exhausted` | | Uuids the server asked the SDK to retry that were never delivered. -`drops` | | `(uuid, details)` pairs the server told the SDK to drop on a 2xx response. -`event_results` | | Each event uuid's last verdict from a 2xx response. - -**Functions:** - -Name | Description ----- | ----------- -`verdict_summary` | Count the undelivered events by verdict and reason, for example - -#### `posthog.capture_send.CaptureError.attempts` - -```python -attempts = attempts -``` - -#### `posthog.capture_send.CaptureError.drops` - -```python -drops = drops or [] -``` - -#### `posthog.capture_send.CaptureError.endpoint` - -```python -endpoint = endpoint -``` - -#### `posthog.capture_send.CaptureError.event_results` - -```python -event_results = event_results or {} -``` - -#### `posthog.capture_send.CaptureError.message` - -```python -message = message -``` - -#### `posthog.capture_send.CaptureError.request_id` - -```python -request_id = request_id -``` - -#### `posthog.capture_send.CaptureError.retry_after` - -```python -retry_after = retry_after -``` - -#### `posthog.capture_send.CaptureError.retry_exhausted` - -```python -retry_exhausted = retry_exhausted or [] -``` - -#### `posthog.capture_send.CaptureError.status` - -```python -status = status -``` - -#### `posthog.capture_send.CaptureError.verdict_summary` - -```python -verdict_summary() -> str -``` - -Count the undelivered events by verdict and reason, for example -`drop/llm_events_over_quota=2, retry/not_persisted=1`. - -Counts the events in `drops` and `retry_exhausted`: events the -server dropped, and events still pending retry after the last 2xx -response. A retry verdict followed by a failed request, for example a -503 on the last attempt, is not counted; `event_results` keeps it. -Empty when no event is counted. - -### `Span` - -A handle to a span. - -Every method is safe to call on any handle, including after `end()` and -on the inert handles returned when tracing is off, so calling code never -branches on whether tracing is running. Entering a handle (`with span:`) -makes it the active span for the block and ends it on exit, recording an -`Exception` raised inside it. A `BaseException` that is not an -`Exception` (`GeneratorExit`, `CancelledError`, `KeyboardInterrupt`) -still ends the span but is not recorded as a failure. - -**Functions:** - -Name | Description ----- | ----------- -`add_event` | Add a timestamped event to the span. Ignored after `end()`. -`end` | End the span and queue it for export. Idempotent. -`record_exception` | Record an exception as an `exception` event and mark the span `error`. -`set_attribute` | Set one attribute on the span; last write wins. Ignored after `end()`. -`set_attributes` | Set several attributes at once; last write wins per key. Ignored after `end()`. -`set_status` | Set the span status to `"ok"` or `"error"`. Ignored after `end()`. -`traceparent` | The W3C `traceparent` header value to propagate, or `None`. -`tracestate` | The W3C `tracestate` header value to propagate, or `None`. -`update_name` | Rename the span, for a name only known after it started. Ignored after `end()`. - -#### `posthog.tracing.span.Span.add_event` - -```python -add_event(name: str, attributes: Optional[Mapping[str, Any]] = None, timestamp: Union[datetime, float, None] = None) -> Span -``` - -Add a timestamped event to the span. Ignored after `end()`. - -`timestamp` defaults to now; it accepts a `datetime`, or seconds -since the epoch as a `float` or `int`. Returns the span, so calls -chain. - -**Examples:** - -```python -span.add_event("cache.miss", {"key": "user:42"}) -``` - -#### `posthog.tracing.span.Span.end` - -```python -end(end_time: Union[datetime, float, None] = None) -> None -``` - -End the span and queue it for export. Idempotent. - -`end_time` defaults to now; it accepts a `datetime`, or seconds -since the epoch as a `float` or `int`, and is never earlier than -the start. Later calls, and later mutations, no-op. `with span:` -ends the span on exit, so call this only for spans started manually. - -**Examples:** - -```python -span = posthog.start_span("job") -try: - run_job() -finally: - span.end() -``` - -#### `posthog.tracing.span.Span.record_exception` - -```python -record_exception(exception: BaseException) -> Span -``` - -Record an exception as an `exception` event and mark the span `error`. - -Ignored after `end()`. The event carries `exception.type`, -`exception.message` and, for a raised exception, -`exception.stacktrace` (its last `max_attribute_value_length` -characters, so a chained traceback keeps the outermost exception and -may lose its cause). Returns the span, so calls chain. Inside -`with span:` a raised `Exception` is recorded automatically, so -this is for exceptions that are caught and handled. - -**Examples:** - -```python -try: - charge(card) -except PaymentError as e: - span.record_exception(e) -``` - -#### `posthog.tracing.span.Span.set_attribute` - -```python -set_attribute(key: str, value: Any) -> Span -``` - -Set one attribute on the span; last write wins. Ignored after `end()`. - -Returns the span, so calls chain. Prefer primitive values: strings, -booleans, integers and floats keep their type. Lists and mappings are -sent as OTLP arrays and maps, but PostHog stores them as serialized -strings. `None` removes the key; anything else is stringified. - -**Examples:** - -```python -span.set_attribute("http.status_code", 200).set_attribute("cache.hit", True) -``` - -#### `posthog.tracing.span.Span.set_attributes` - -```python -set_attributes(attributes: Mapping[str, Any]) -> Span -``` - -Set several attributes at once; last write wins per key. Ignored after `end()`. - -Returns the span, so calls chain. Same encoding as `set_attribute()`. - -**Examples:** - -```python -span.set_attributes({"db.system": "postgresql", "db.rows": 12}) -``` - -#### `posthog.tracing.span.Span.set_status` - -```python -set_status(code: str, message: Optional[str] = None) -> Span -``` - -Set the span status to `"ok"` or `"error"`. Ignored after `end()`. - -Unset by default. Any other `code` is ignored. `ok` is final for the -scoped form: an `Exception` raised inside `with span:` does not override -it. Returns the span, so calls chain. - -**Examples:** - -```python -span.set_status("error", "upstream timed out") -``` - -#### `posthog.tracing.span.Span.traceparent` - -```python -traceparent() -> Optional[str] -``` - -The W3C `traceparent` header value to propagate, or `None`. - -#### `posthog.tracing.span.Span.tracestate` - -```python -tracestate() -> Optional[str] -``` - -The W3C `tracestate` header value to propagate, or `None`. - -#### `posthog.tracing.span.Span.update_name` - -```python -update_name(name: str) -> Span -``` - -Rename the span, for a name only known after it started. Ignored after `end()`. - -Returns the span, so calls chain. - -**Examples:** - -```python -span = posthog.start_span("http.request") -span.update_name(f"{request.method} {route.pattern}") -``` - -### `Emails` - -```python -Emails(secret_key: Optional[str], *, host: Optional[str] = None, timeout: float = 15, max_retries: int = 2, disabled: bool = False, session: Optional[requests.Session] = None) -> None -``` - -Bases: `_EmailsBase` - -Sends transactional email from PostHog email templates. - -`host` is your PostHog app host. PostHog Cloud ingestion hosts map to the -app host, so `us.i.posthog.com` sends to `us.posthog.com`. Any other -host is used as given. - -Each send retries network errors, rate limits and an unavailable service up -to `max_retries` times, with the same idempotency key. Set -`max_retries=0` when a job runner already retries the send. `timeout` -is in seconds, per request. Pass `session` to send through your own -`requests.Session`, for example a test double. - -**Functions:** - -Name | Description ----- | ----------- -`close` | Close the HTTP connections this client opened. A `session` you passed in stays open. -`get` | Read what happened to an email that `send` queued. -`send` | Queue one email from a transactional email template. - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`disabled` | | -`host` | | -`max_retries` | | -`secret_key` | | -`timeout` | | - -#### `posthog.emails.Emails.close` - -```python -close() -> None -``` - -Close the HTTP connections this client opened. A `session` you passed in stays open. - -#### `posthog.emails.Emails.disabled` - -```python -disabled = disabled -``` - -#### `posthog.emails.Emails.get` - -```python -get(message_id: Union[str, UUID]) -> EmailStatus -``` - -Read what happened to an email that `send` queued. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`message_id` | `Union[str, UUID]` | The `id` of the `SendEmailResult` that `send` returned. | *required* - -**Returns:** - -Type | Description ----- | ----------- -`EmailStatus` | The email's latest status, for 30 days after the send. - -**Raises:** - -Type | Description ----- | ----------- -`EmailError` | PostHog did not answer with a status. `code` is `not_found` for an unknown ID, an email older than 30 days, or for a few seconds after the send. -`ValueError` | `message_id` is not a message ID. - -**Examples:** - -```python -result = emails.send(to="user@example.com", template_id="...") -status = emails.get(result.id) -if status.status == "bounced": - print(status.reason) -``` - -#### `posthog.emails.Emails.host` - -```python -host = determine_app_host(host) -``` - -#### `posthog.emails.Emails.max_retries` - -```python -max_retries = max_retries -``` - -#### `posthog.emails.Emails.secret_key` - -```python -secret_key = secret_key.strip() if isinstance(secret_key, str) else None -``` - -#### `posthog.emails.Emails.send` - -```python -send(*, to: str, template_id: Union[str, UUID], template_data: Optional[Mapping[str, Any]] = None, distinct_id: Optional[str] = None, idempotency_key: Optional[str] = None, max_retries: Optional[int] = None) -> SendEmailResult -``` - -Queue one email from a transactional email template. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`to` | `str` | One recipient email address. | *required* -`template_id` | `Union[str, UUID]` | The UUID of a template made transactional in PostHog. | *required* -`template_data` | `Optional[Mapping[str, Any]]` | Values for the template's `{{ variables.* }}` placeholders. Values are encoded the way capture encodes properties. | `None` -`distinct_id` | `Optional[str]` | The recipient's distinct ID in your product. The email's events go to this person. Without it, they go to a person whose distinct ID is the email address. | `None` -`idempotency_key` | `Optional[str]` | 1 to 255 visible ASCII characters. A send with a key that PostHog has already accepted queues nothing and returns the first send's result. Without it, the client makes a key that covers its own retries. Pass a key derived from what the email is for, such as `welcome:user_123`, to cover retries outside this call, too. | `None` -`max_retries` | `Optional[int]` | Overrides the client's maximum number of retries for this send. `0` turns retries off. | `None` - -**Returns:** - -Type | Description ----- | ----------- -`SendEmailResult` | The queued email. A result means PostHog queued the email, not that -`SendEmailResult` | it was delivered. - -**Raises:** - -Type | Description ----- | ----------- -`EmailError` | PostHog did not queue the email, or the client could not tell whether it did. -`(TypeError, ValueError)` | An argument has the wrong type or format. - -**Examples:** - -```python -emails.send( - to="user@example.com", - template_id="0190f6d2-7c1e-7b3a-9f5e-1c2d3e4f5a6b", - template_data={"name": "Ada"}, - distinct_id="user_123", - idempotency_key="welcome:user_123", -) -``` - -#### `posthog.emails.Emails.timeout` - -```python -timeout = timeout -``` - -### `EmailError` - -```python -EmailError(*, name: str, code: str, message: str, status_code: Optional[int] = None, why: Optional[str] = None, fix: Optional[str] = None, docs_url: Optional[str] = None, field: Optional[str] = None, retry_after: Optional[float] = None) -> None -``` - -Bases: `Exception` - -PostHog did not queue the email, or the client could not tell whether it did. - -Branch on `code`, PostHog's stable error code, or on `name`, a coarse -category of the failure. The message never contains the secret key or the -request body. - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`name` | | A coarse category of the failure. -`code` | | PostHog's stable error code. -`message` | | What went wrong. It never contains the secret key or the request body. -`status_code` | | The HTTP status of the response, or `None` when none arrived. -`why` | | PostHog's explanation of the failure, when it gave one. -`fix` | | What to change to fix it, when PostHog said. -`docs_url` | | A docs page about the error, when PostHog gave one. -`field` | | The request field the error is about, when there is one. -`retry_after` | | Seconds to wait before retrying, when PostHog said. - -#### `posthog.emails.EmailError.code` - -```python -code = code -``` - -#### `posthog.emails.EmailError.docs_url` - -```python -docs_url = docs_url -``` - -#### `posthog.emails.EmailError.field` - -```python -field = field -``` - -#### `posthog.emails.EmailError.fix` - -```python -fix = fix -``` - -#### `posthog.emails.EmailError.message` - -```python -message = message -``` - -#### `posthog.emails.EmailError.name` - -```python -name = name -``` - -#### `posthog.emails.EmailError.retry_after` - -```python -retry_after = retry_after -``` - -#### `posthog.emails.EmailError.status_code` - -```python -status_code = status_code -``` - -#### `posthog.emails.EmailError.why` - -```python -why = why -``` - -### `SendEmailResult` - -```python -SendEmailResult(id: str, replayed: bool = False, warnings: tuple[EmailWarning, ...] = ()) -> None -``` - -The queued email. `id` is its message ID. - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`id` | `str` | The email's message ID. -`replayed` | `bool` | `True` when the response says PostHog replayed an earlier send for the same `idempotency_key` instead of sending again. -`warnings` | `tuple[EmailWarning, ...]` | Warnings PostHog returned about the request. - -#### `posthog.emails.SendEmailResult.id` - -```python -id: str -``` - -#### `posthog.emails.SendEmailResult.replayed` - -```python -replayed: bool = False -``` - -#### `posthog.emails.SendEmailResult.warnings` - -```python -warnings: tuple[EmailWarning, ...] = () -``` diff --git a/references/posthog-python-migration.md b/references/posthog-python-migration.md deleted file mode 100644 index 1c6c18564..000000000 --- a/references/posthog-python-migration.md +++ /dev/null @@ -1,269 +0,0 @@ ---- -title: PostHog Python SDK migration guides -sidebar: Docs -showTitle: true ---- - -# Migrating from posthog 7.x to 8.0 - -posthog 8.0 sends every event through capture v1 and removes the legacy capture path. -Most apps upgrade without code changes. -Read the checklist first, then the sections that apply to you. - -For the changes every capture v1 SDK shares, such as event options, limits and per-event results, see [Upgrading to capture v1 SDKs](https://posthog.com/docs/libraries/capture-v1). - -## Checklist - -You need to change code if your app does any of these: - -- passes `Client` or `AsyncPosthog` constructor arguments by position after `host` -- calls `posthog.request.post` or `posthog.request.flags` with positional arguments after `path` or `host` -- sets `$exception_level` or another reserved exception property in `capture_exception(properties=...)` -- sets `capture_mode`, `POSTHOG_CAPTURE_MODE` or `gzip` -- imports `CaptureMode`, `posthog.capture_mode`, `CaptureV1Error`, `posthog.capture_v1`, `request.batch_post`, `EVENTS_ENDPOINT` or `AI_EVENTS_ENDPOINT` -- sends events to a self-hosted PostHog that does not serve the capture v1 endpoints -- sends events through a proxy that redirects capture requests to another host -- reuses one event `uuid` for more than one event -- sets `$process_person_profile` to turn person processing on for events without a distinct ID -- passes strings such as `"true"` for `$cookieless_mode`, `$ignore_sent_at` or `$process_person_profile` -- expects `super_properties` to override properties passed to a single call, including `$set` and `$groups` -- sets `$is_server`, `$geoip_disable` or a system property such as `$os` and expects the SDK's value to win -- tests the AI integrations with a mock client and asserts on `capture` -- passes its own client object to the AI integrations -- sets `$lib` or `$lib_version`, or filters on `$lib = "posthog-python-mcp"` -- passes a personal API key (`phx_...`) as the project API key -- reads event content from the SDK's debug log -- calls `alias()` without a `distinct_id` and relies on the context distinct ID -- runs the Django, Flask or ASGI integration on 7.68 to 7.70 without tracing headers turned on (they are now read by default) - -## Endpoints - -| Method | Endpoint | -| --- | --- | -| `capture`, `set`, `set_once`, `alias`, `group_identify`, `capture_exception` | `/i/v1/analytics/events` | -| `capture_ai`, `capture_ai_immediate`, and the AI wrapper integrations | `/i/v1/ai/events` | -| OpenTelemetry (`posthog.ai.otel`) | `/i/v0/ai/otel`, unchanged | - -If you send events to a self-hosted PostHog, check that it serves both endpoints before you upgrade. -An endpoint that is not served drops every event sent to it. - -If you send events through your own reverse proxy, update it before you upgrade: - -- Forward `/i/v1/analytics/events` and `/i/v1/ai/events` to PostHog. -- Pass `Authorization` (the project token, sent as `Bearer `), every `PostHog-*` header, `Content-Type`, `Content-Encoding` and `User-Agent` through unchanged. PostHog answers `401` without `Authorization` and `400` without the others, and the batch fails. -- Accept request bodies up to 20 MB. AI requests are often larger than 1 MB, the default limit in nginx and ingress-nginx. A `413` from the proxy fails the batch. - -PostHog's managed reverse proxy already does this. See the [proxy guides](https://posthog.com/docs/advanced/proxy). - -The SDK follows a `307` or `308` redirect only to the origin of `host` (same scheme, host and port), at most 5 times. -Any other redirect fails the batch and reaches `on_error`. -In 7.x the sync client followed redirects to any origin. -If a proxy redirects capture requests to another host, point `host` at the final host. - -## SDK identity - -PostHog sets `$lib` and `$lib_version` on every event from the `PostHog-Sdk-Info` request header, which is always `posthog-python/`. -The SDK removes `$lib` and `$lib_version` from the properties it sends. -A value you set in a call, in `super_properties` or in `before_send` does not reach PostHog. - -MCP instrumentation no longer relabels the client. -In 7.x, `posthog.mcp.instrument()` and `PostHogMCP` set the client's identity to `posthog-python-mcp`. -With the default client, or any client the app also used, every event and feature flag request from the app then reported `posthog-python-mcp`, not only the MCP events. -In 8.0, MCP events report `posthog-python` like all other events. -To find MCP traffic, filter on the `$mcp_*` events and properties instead of `$lib`. - -## Removed options and APIs - -| Removed | Use instead | -| --- | --- | -| `capture_mode`, `CaptureMode`, `POSTHOG_CAPTURE_MODE` | Nothing. Capture v1 is the only path. Setting `posthog.capture_mode` on the module has no effect. | -| `gzip=True` | `capture_compression=CaptureCompression.GZIP`. The default is no compression. `POSTHOG_CAPTURE_COMPRESSION` still works. | -| `CaptureV1Error` (`from posthog.capture_v1 import CaptureV1Error`) | `CaptureError` (`from posthog import CaptureError`). There is no alias. | -| `posthog.capture_v1` | `posthog.capture_event` and `posthog.capture_send` | -| `request.batch_post`, `async_batch_post`, `EVENTS_ENDPOINT`, `AI_EVENTS_ENDPOINT` | Nothing. Send events through a client. | -| The `gzip` parameter of `request.post` and `request.flags` | Nothing. The parameters after `path` in `post` and after `host` in `flags` are keyword-only, so a 7.x call that passes them by position raises `TypeError`. A `gzip=` keyword raises no error: it is sent as a field in the request body. | -| The `backoff` dependency | Add it to your own requirements if your code imports it. | - -The `posthoganalytics` package has the same changes. For example, import `CaptureError` from `posthoganalytics`. - -Constructor arguments after `host` are keyword-only: - -```python -# 7.x -Posthog("", "https://us.i.posthog.com", True) - -# 8.0 -Posthog("", host="https://us.i.posthog.com", debug=True) -``` - -## Errors and `on_error` - -Capture v1 returns a result for every event, even when the request succeeds. -An event can be dropped inside a 2xx response, for example when it is over a product quota. -8.0 reports those events as failures. -When the whole request is over the billing limit, capture returns a `402` with no per-event results instead. - -- Every capture failure is a `CaptureError`, a subclass of `APIError`. It has these fields: - - `status`: the HTTP status, or `0` when the request never got a response. With `0`, `__cause__` holds the network error. In 7.x, `on_error` received the raw `requests` exception. - - `endpoint`, `request_id` and `attempts` - - `drops`: `(uuid, details)` pairs for the events capture dropped in a 2xx response - - `retry_exhausted`: the uuids still waiting for a retry after the last attempt - - `event_results`: a `CaptureEventResult(result, details)` for each uuid that got a result in a 2xx response, from `posthog.capture_send`. An event missing from it never got a result. - - `verdict_summary()`: counts such as `drop/llm_events_over_quota=1, retry/not_persisted=1` -- `on_error(error, batch)` receives every failure, including in `sync_mode` and from `AsyncPosthog.capture_immediate`. -- With `on_error` set, the SDK logs nothing for a failed batch. -- Without `on_error`, the SDK logs one line per failed batch, for example `2 event(s) not persisted by /i/v1/analytics/events: ...`. The line never contains event content or the server's response text. After a 2xx response it also counts the lost events by PostHog's reason, for example `drop/event_too_big=1`. -- The 7.x lines `error uploading: ...`, `async capture upload failed (...)` and `Immediate async capture failed (...)` are gone. In `sync_mode`, a failed request no longer logs the 7.x `Error in capture: ...` line, or the same line for another method. Update any log-based alerts that match these lines. -- In `sync_mode`, a failed `capture()` calls `on_error` and returns `None`. With `debug=True` it calls `on_error` and then re-raises the error. -- In `sync_mode`, a failed request is retried like a background batch, in the calling thread. With the default `max_retries` of 3, a call makes up to 4 attempts, and each attempt can take up to `timeout` (`capture_ai_timeout` for AI events). With the defaults, an analytics event whose requests keep timing out blocks the call for about a minute. 7.x made one attempt. -- If a capture inside `on_error` fails, the SDK logs the failure and does not call `on_error` again. -- The SDK retries `404`, `408`, `429`, `500`, `502`, `503` and `504` responses and network errors. Other error statuses fail the batch at once, including the other 5xx codes. 7.x retried `429` and every 5xx, but not `404`. Capture itself does not return `429`; a proxy in front of it can. A `404` that lasts through every attempt can mean the PostHog deployment has no capture v1 route, for example an older self-hosted install. -- Each attempt makes one connection try. 7.x also retried a failed connection up to twice inside each attempt, so a capture request could block for up to three times the `timeout` per attempt. -- Retries start at 100 ms and double each attempt, up to 30 seconds. Each wait varies randomly by up to 25%, so many clients do not retry at the same moment. A `Retry-After` header sets a minimum wait, up to 30 seconds. The `Consumer` default is 3 retries, down from 10. In 7.x the first retry waited up to 1 second. -- The SDK's own log lines no longer contain event content: the debug line for a queued event names the event and the lane, and the invalid timestamp warning no longer includes the value. When your `before_send` hook raises, the error line names the event and the exception type, not the exception message, which can hold the data the hook was removing. The traceback is logged at debug level. To inspect events, use `before_send`. -- A personal API key (`phx_...`) passed as the project API key disables the client and logs one error, as an empty key does. Capture rejects personal API keys, and the SDK no longer sends one with every request. - -## Invalid events - -Capture rejects a whole batch when one event cannot be parsed. -8.0 drops only the invalid event, so the rest of the batch is delivered. - -- An event whose name is missing, `None`, empty or not a string is dropped. The SDK logs one warning per reason per process. 7.x sent it. -- An event whose `before_send` hook sets `distinct_id` to `None` or a non-string is dropped, with one warning per reason per process. The SDK does not convert the hook's value or make up an ID. 7.x sent it, and capture would reject the whole batch. -- PostHog drops an event whose name is over 200 UTF-8 bytes (`event_name_too_long`), and `on_error` reports it. 7.x sent events to legacy capture, which had no event name limit. -- A `NaN` or infinite `float` value is sent as `null`, as in 7.75 and later, and the event is kept. Earlier 7.x versions sent invalid JSON, which made capture reject the whole batch. A `NaN` value in `$set` sets that person property to `null`. -- An event that cannot be serialized to JSON, for example one with a circular reference, is dropped and logged. As in 7.x, a circular reference logs `Error in capture: maximum recursion depth exceeded` with a long traceback. -- A property or option key that JSON cannot represent, such as a tuple, is dropped without a log, and the rest of the event is sent. 7.x dropped the event. `int`, finite `float`, `bool` and `None` keys are still converted to strings. A `NaN` or infinite `float` key still drops the event. - -## Event uuids - -- Each event needs its own uuid. Capture keeps the first event for each uuid in a request and drops the later copies. `on_error` reports each dropped copy in `CaptureError.drops` as `(uuid, "duplicate_event_uuid")`. 7.x sent every copy. Older PostHog versions reject the whole batch with a 400 `duplicate_event_uuid` instead. -- The SDK accepts a uuid with hyphens, 32 hex digits, `{...}` braces or a `urn:uuid:` prefix, in any case. It sends the lowercase hyphenated form and returns that form from `capture`. -- Any other value is replaced with a generated uuid, and the SDK logs one warning that names the rule, not the value. An empty string counts as unset, so the SDK generates a uuid without a warning. This applies to `AsyncPosthog` too. -- Generated uuids are UUIDv7. - -## Session and window IDs - -Capture v1 sends `$session_id` and `$window_id` as top-level event fields, not as properties. -The SDK moves them out of `properties` for you. - -- A string is sent as given, including `""`. -- `None` counts as unset and is removed. -- Any other value is removed and not sent, because capture would reject the whole batch. 7.x sent it as a property. The SDK logs a warning for each one, with the key and the value's type but not the value. - -## Identity and request context - -- An empty or whitespace-only `distinct_id` counts as missing, so the event gets a generated personless distinct ID. 7.x sent a whitespace-only ID, and capture dropped the event. -- Generated personless distinct IDs are UUIDv7. 7.x generated UUIDv4. -- `group_identify` uses the `distinct_id` you pass, then the context distinct ID, then `$_`. 7.x fell back to a random ID, which created a new person on every call. If `$_` is over 200 bytes, pass `distinct_id`: PostHog drops an event whose distinct ID is over 200 bytes. -- PostHog drops an event whose `distinct_id` is over 200 UTF-8 bytes (`distinct_id_too_large`), and `on_error` reports it. 7.x sent events to legacy capture, which shortened the ID to 200 characters and kept the event. -- `group_identify` no longer adds the context session ID. A session takes the person of its latest event, and this event's distinct ID is not the session's user. -- `alias()` needs an explicit `distinct_id`. It no longer falls back to the context distinct ID, which can come from a request header the client controls. Without one, the call is dropped with a warning. -- Context tags and context options now apply to `alias` and `group_identify`, below the call's own values, as they do for `capture`. -- Person processing turned off in the context, with `set_context_option("process_person_profile", False)` or a `$process_person_profile` tag, or in `super_options`, now applies to `$create_alias` and `$groupidentify` events too. A `$process_person_profile: False` in `super_properties` already did this in 7.x. PostHog drops these events when person processing is off, with an ingestion warning. Capture accepts them, so `on_error` does not report the drop. To keep them, turn person processing off per call instead. - -## Tracing headers - -The Django, Flask and ASGI integrations read the `X-PostHog-Distinct-Id` and `X-PostHog-Session-Id` headers by default, as in 7.71 and later. -The headers only fill a missing distinct ID or session ID, and any client can set them, so never use them for authentication. - -- To turn this off, set `POSTHOG_MW_CAPTURE_TRACING_HEADERS = False` in Django, or pass `capture_tracing_headers=False` to the Flask or ASGI integration. -- The 7.68 `trust_tracing_headers` and `POSTHOG_MW_TRUST_TRACING_HEADERS` names still work, but are deprecated. -- If you upgrade from 7.68, 7.69 or 7.70 and never turned these headers on, they are now read. - -## Exceptions - -- `capture_exception` takes a `level=` argument on `Client`, `AsyncPosthog` and the module. It sets `$exception_level`, for example `"warning"` or `"fatal"`. The default is `"error"`, and an unknown value counts as unset. -- `capture_exception` ignores reserved exception properties in `properties`, such as `$exception_list`, `$exception_level` and `$exception_source`. In 7.x they overrode the SDK's values, with a `DeprecationWarning`. Use `level=` instead of `$exception_level`. -- `AsyncPosthog.capture_exception` now sends `$exception_level`. -- `AsyncPosthog.capture_exception` now adds `$trace_id` and `$span_id` from the active OpenTelemetry span, like `Client.capture_exception`. In 7.x it added them only with `capture_trace_context=True`. - -## Event options - -Capture v1 sends processing options in an `options` object, next to `properties`. - -- `capture`, `capture_ai`, `set`, `set_once`, `alias`, `group_identify` and `capture_exception` take an `options` argument: - - ```python - posthog.capture("signed_up", distinct_id="user-1", options={"cookieless_mode": True}) - ``` - -- `super_options`, a new `Client`, `AsyncPosthog` and module setting, sets options on every event, like `super_properties`. -- `set_context_option(key, value)` sets an option for the current context, like `tag()`. `get_context_options()` returns the options of the current context. Both exist on `Client` and as module functions. `AsyncPosthog` has no context methods, like it has no `tag()`, so call the module functions. They apply to every client in the context. -- Context tags and options reach `capture`, `capture_ai`, `capture_exception`, `alias` and `group_identify`. Context options also reach `set` and `set_once`. `super_properties` and `super_options` reach every method. -- Options are sent as given. PostHog validates them. It drops an event with an option value it cannot read, such as a list for `cookieless_mode`, as `invalid_options`, and `on_error` reports it. -- An `options` value that is not a dict is logged as an error and ignored. The event is still sent. -- The legacy option properties, the `$`-prefixed properties legacy capture reads as settings, still work. They fill the matching option only when it is unset, and the SDK always removes them from `properties`. Their values are no longer converted, so pass `True` or `False`, not `"true"`. - - | Legacy option property | Option | - | --- | --- | - | `$cookieless_mode` | `cookieless_mode` | - | `$ignore_sent_at` | `disable_skew_correction` | - | `$product_tour_id` | `product_tour_id` | - | `$process_person_profile` | `process_person_profile` | - -- An option set at any layer wins over its legacy property set at any layer. For example, `super_options={"cookieless_mode": True}` wins over an event's `$cookieless_mode: False`. When you move a default to options, move the per-event overrides of that key to options too. -- A `None` option counts as unset, so a later step can fill it. - -Values apply in this order. Steps 2 to 4 fill only the options and properties that the steps before them left unset: - -1. the `options` and `properties` of the call, then the call's `disable_geoip` argument, which fills `$geoip_disable` -2. context options and tags -3. `super_options` and `super_properties` -4. values the SDK sets: `$is_server` from `is_server`, `$geoip_disable` from the client's `disable_geoip` setting, system properties such as `$os` and `$python_version`, the `$feature/` and `$active_feature_flags` properties from `flags` or `send_feature_flags`, `$trace_id` and `$span_id` from the active OpenTelemetry span (in `capture` and `capture_ai` only with `capture_trace_context=True`, and always in `capture_exception`), `options.process_person_profile = false` for events without a distinct ID, and `$release_id` from `POSTHOG_RELEASE_ID` -5. `before_send`, which sees the result of steps 1 to 4 and can change or remove any of it -6. legacy properties, which fill unset options and are then removed - -A property set to `None` counts as set, so no later step fills it. -`$set`, `$set_once`, `$groups` and `$group_set` fill one level deep. -When the call and a later step both set one of them to a dict, the later step adds only the keys the call left out. -For example, `super_properties={"$set": {"plan": "free", "source": "web"}}` and a call with `properties={"$set": {"plan": "pro"}}` send `{"plan": "pro", "source": "web"}`. - -These changes follow from this order: - -- Properties passed to a call now override `super_properties`. `super_properties` can no longer change `$lib` or `$lib_version`. -- A `$is_server`, `$geoip_disable` or system property such as `$os` that you set in a call or a context tag now wins over the SDK's value. In 7.x the SDK overwrote it. `super_properties` win over the SDK's value too, so `super_properties={"$geoip_disable": False}` turns GeoIP lookup on for events, even with `disable_geoip=True`. -- Feature flag properties from `flags` or `send_feature_flags` still lose to the same key in the call's `properties`, a context tag or `super_properties`, as in 7.x. -- A `$trace_id` or `$span_id` in a context tag or `super_properties` now replaces the active OpenTelemetry span's IDs. In 7.x the span's IDs beat context tags. Setting either key in the call's `properties`, a context tag or `super_properties` stops the SDK adding both. In 7.x the SDK still added the other key. -- A `disable_geoip` argument to a call belongs to that event, so it wins over context tags and `super_properties`. A `$geoip_disable` in the call's own `properties` still wins over it. `disable_geoip=False` now sends `$geoip_disable: false`. In 7.x it sent no `$geoip_disable` property. -- The `groups` argument merges into a `$groups` property of the call, and wins key by key. In 7.x it replaced the property. MCP events merge their identity's groups into a custom `$groups` property the same way. -- A `$set`, `$set_once`, `$groups` or `$group_set` in `super_properties` no longer replaces the whole value of the call. The two merge, and the call wins key by key. -- An event without a distinct ID gets `options.process_person_profile = false`. A `$process_person_profile: true` property no longer turns person processing back on. Set the option instead. -- Review your `before_send` hooks for the new event shape. To change an option, edit `options`: a legacy property that `before_send` adds does not replace an option that is already set. A hook that returns a new dict must copy `options`, or the event is sent without them. That includes the `process_person_profile = false` the SDK sets for an event without a distinct ID, so PostHog would create a person for its generated ID. -- When the server enables minimal `$feature_flag_called` events, those events now keep `$window_id` and the legacy option properties, so their options still apply. - -## AI capture - -- `capture_ai` and the AI wrapper integrations send to `/i/v1/ai/events`. The AI endpoint accepts events up to 8 MiB. -- Send `$ai_` events with `capture_ai`. `capture()` sends every event to `/i/v1/analytics/events`, including `$ai_` events, which PostHog accepts there for now. -- `capture_ai` sends any event name to the AI endpoint. PostHog drops an event whose name does not start with `$ai_` as `misrouted_event`, and `on_error` reports it. The SDK writes only a debug log. -- The OpenTelemetry integration (`PostHogSpanProcessor`, `PostHogTraceExporter`) is unchanged: it still exports spans over OTLP to `/i/v0/ai/otel`. -- `enable_full_ai_capture` now controls only content: string truncation and media redaction. It no longer chooses the endpoint. `_use_ai_lane` and `_enable_multimodal_capture` still work as aliases. -- The AI integrations call your client's `capture_ai`, and never `capture`. A custom client object must implement `capture_ai` and accept its `options` keyword argument. If the client has no `capture_ai`, the wrapper, callback handler or tracing processor logs one error when you create it and captures no AI events; provider calls still work. Before, such a client got `capture` calls. -- Tests that pass a `Mock` client to an AI integration must assert on `mock.capture_ai`, not `mock.capture`. -- When an AI integration or MCP falls back to a trace, run or session ID, it turns person processing off with a per-event option. This is the only option the integrations pass. It wins over `super_options`, context options and a `$process_person_profile: true` property. `before_send` can still change it. -- An empty or whitespace-only distinct ID passed to an AI integration, such as `posthog_distinct_id=""`, counts as missing, so the integration falls back the same way and turns person processing off. In 7.x, an empty ID gave non-streaming wrapper calls a random personless ID. Streaming calls, embeddings and LangChain used the trace or run ID with person processing on. OpenAI Agents already treated an empty ID as missing. Every integration sent a whitespace-only ID as given, with person processing on. -- The AI integrations have no `options` argument. To set options on their events, use `set_context_option`, `super_options`, or a legacy property such as `$cookieless_mode` in `posthog_properties`. -- MCP's `PostHogCaptureEvent` has an `options` key. -- New `Client` and `AsyncPosthog` arguments for the AI lane: - - `capture_ai_compression`: default none. `POSTHOG_CAPTURE_COMPRESSION` does not apply to it. - - `capture_ai_max_queue_size`: default 1000 - - `capture_ai_timeout`: default 30 seconds - - `capture_ai_max_event_bytes`: default 8 MiB plus 64 KiB. You can only lower it. -- The AI lane does not use `max_queue_size`, `timeout` or `capture_compression`. An invalid `capture_ai_*` value raises `ValueError` when you create the client. -- The module-level client has no AI lane settings. To change them, create a `Client`. -- `AsyncPosthog` has `capture_ai` and `capture_ai_immediate`. -- `AsyncPosthog` sends events with `requests` in a worker thread. It uses `httpx` only for feature flags and remote config. -- `AsyncPosthog` accepts `privacy_mode` and `enable_full_ai_capture`, with the same meaning as on `Client`. An AI integration given an `AsyncPosthog` client used to always truncate and redact media. - -## Batching and size limits - -- An analytics event over 10 MiB is dropped and logged, because no capture request can carry it. 7.x dropped events over 900 KiB. Capture drops an event over its own per-event limit and reports it in `CaptureError.drops` as `event_too_big`. -- An AI event over `capture_ai_max_event_bytes` is dropped and logged. -- A batch stops before an event that would take it past 5 MiB. A larger event is sent alone. -- `flush()` and `shutdown()` drain the analytics and AI queues at the same time. - -## OpenFeature provider - -`openfeature-provider-posthog` 0.2.0 works with posthog 7.x and 8.x. -Older provider versions require posthog below 8.0, so upgrade the provider before or with posthog. diff --git a/references/posthog-python-reference.md b/references/posthog-python-reference.md deleted file mode 100644 index 77b863554..000000000 --- a/references/posthog-python-reference.md +++ /dev/null @@ -1,4102 +0,0 @@ ---- -title: "PostHog Python SDK" -sidebar: Docs -showTitle: true -contentMaxWidthClass: max-w-full -platformLogo: python -tableOfContents: - - value: "Getting started" - url: getting-started - depth: 0 - - value: "Traces" - url: traces - depth: 0 - - value: "Alias" - url: alias - depth: 0 - - value: "Capture" - url: capture - depth: 0 - - value: "Capture AI" - url: capture-ai - depth: 0 - - value: "Capture exception" - url: capture-exception - depth: 0 - - value: "Emails" - url: emails - depth: 0 - - value: "Flush" - url: flush - depth: 0 - - value: "Get feature flag" - url: get-feature-flag - depth: 0 - - value: "Get feature flag payload" - url: get-feature-flag-payload - depth: 0 - - value: "Get feature flag result" - url: get-feature-flag-result - depth: 0 - - value: "Get feature flags" - url: get-feature-flags - depth: 0 - - value: "Get feature flags and payloads" - url: get-feature-flags-and-payloads - depth: 0 - - value: "Evaluate flags" - url: evaluate-flags - depth: 0 - - value: "Group identify" - url: group-identify - depth: 0 - - value: "Identify" - url: identify - depth: 0 - - value: "Is feature enabled" - url: is-feature-enabled - depth: 0 - - value: "Setup" - url: setup - depth: 0 - - value: "Shutdown" - url: shutdown - depth: 0 - - value: "Flag definition loader" - url: flag-definition-loader - depth: 0 - - value: "SDK utils" - url: sdk-utils - depth: 0 - - value: "AsyncClient" - url: asyncclient - depth: 0 - - value: "Module functions" - url: module-functions - depth: 0 - - value: "Types" - url: types - depth: 0 ---- - -## Getting started - - -The Python SDK captures events, evaluates feature flags and reports errors from Python 3.10 or later. - -### Install - -```bash -pip install posthog -``` - -### Initialize - -Create one client for your app with your project token and host. Your project token and host are in your [project settings](https://app.posthog.com/project/settings). Keep the token in an environment variable rather than in code. - -```python -from posthog import Posthog - -posthog = Posthog("", host="") -``` - -### Capture an event - -```python -posthog.capture("user_signed_up", distinct_id="user_123", properties={"plan": "free"}) -``` - -`capture` queues the event and returns right away. A background thread sends it. - -### Shut down - -Call `shutdown()` before your process exits, so queued events are sent. - -```python -posthog.shutdown() -``` - -In an asyncio app, use `AsyncPosthog` as an async context manager instead. See [AsyncClient](#asyncclient). - -## Traces - -### `Client.start_span` - -```python -start_span(name: str, *, kind: Optional[str] = None, attributes: Optional[Mapping[str, Any]] = None, parent: Union[Span, str, None] = None, tracestate: Optional[str] = None, start_time: Union[datetime, float, None] = None) -> Span -``` - -Start a span for distributed tracing. Alpha. - -Returns a span handle. Use it as a context manager to make it the active -span for the block and end it on exit (recording a raised exception on -the way out); or call `end()` yourself for a span that cannot wrap a -block. Spans started inside the block nest under it automatically. -Always returns a usable handle, even when tracing is off, so calling -code never branches. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`name` | `str` | A low-cardinality operation name, e.g. `GET /users/:id`. Variable values belong in attributes, not the name. | *required* -`kind` | `Optional[str]` | `internal` (default), `server`, `client`, `producer` or `consumer`. | `None` -`attributes` | `Optional[Mapping[str, Any]]` | Initial attributes. | `None` -`parent` | `Union[Span, str, None]` | A span handle, or an inbound W3C `traceparent` header value to continue a remote trace. Defaults to the active span. A forked child starts with no active span; pass the parent span to continue a trace across a fork. | `None` -`tracestate` | `Optional[str]` | The inbound `tracestate` header accompanying a `traceparent` string `parent`; preserved and propagated. | `None` -`start_time` | `Union[datetime, float, None]` | A `datetime` or epoch seconds, to backdate the span. | `None` - -**Examples:** - -```python -posthog = Posthog("", traces={"service_name": "checkout-api"}) - -with posthog.start_span("POST /checkout", parent=request.headers.get("traceparent")) as span: - span.set_attribute("plan", user.plan) - with posthog.start_span("db.query", kind="client"): - ... - outgoing_headers = {"traceparent": span.traceparent()} -``` - -### `Client.get_active_span` - -```python -get_active_span() -> Optional[Span] -``` - -The span that is active in the current context, or `None`. Alpha. - -Only entering a span (`with posthog.start_span(...) as span:`) makes -it active; a span started manually is not. Use it to propagate the -trace to the next service: `span.traceparent()` is the header value. - -**Examples:** - -```python -span = posthog.get_active_span() -if span is not None: - headers["traceparent"] = span.traceparent() -``` - -## Alias - -### `Client.alias` - -```python -alias(previous_id: ID_TYPES, distinct_id: Optional[str], timestamp: Optional[Union[datetime, str]] = None, uuid: Optional[str] = None, disable_geoip: Optional[bool] = None) -> Optional[str] -``` - -Create an alias between two distinct IDs. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`previous_id` | `ID_TYPES` | The previous distinct ID. Required - the call is dropped with a warning if it is missing or empty. | *required* -`distinct_id` | `Optional[str]` | The new distinct ID to alias to. Falls back to the context distinct ID; the call is dropped with a warning if neither is available. | *required* -`timestamp` | `Optional[Union[datetime, str]]` | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | `None` -`uuid` | `Optional[str]` | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | `None` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this event. | `None` - -**Examples:** - -```python -posthog.alias(previous_id='distinct_id', distinct_id='alias_id') -``` - -Note: This method will not raise exceptions. Errors are logged. - -## Capture - -### `Client.capture` - -```python -capture(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] -``` - -Captures an event manually. [Learn about capture best practices](https://posthog.com/docs/product-analytics/capture-events) - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`event` | `str` | The event name to capture. | *required* -`distinct_id` | | The distinct ID of the user. | -`properties` | | A dictionary of properties to include with the event. | -`timestamp` | | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | -`uuid` | | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | -`groups` | | A dictionary of group information. | -`flags` | | A FeatureFlagEvaluations snapshot from evaluate_flags(). The exact values from the snapshot are attached with no extra /flags request. | -`send_feature_flags` | | Deprecated. Prefer flags=... from evaluate_flags(). When truthy, evaluates flags during capture and attaches them to the event. | -`disable_geoip` | | Whether to disable GeoIP for this event. | - -**Examples:** - -```python -# Anonymous event -posthog.capture('some-anon-event') -``` -```python -# Context usage -from posthog import identify_context, new_context -with new_context(): - identify_context('distinct_id_of_the_user') - posthog.capture('user_signed_up') - posthog.capture('user_logged_in') - posthog.capture('some-custom-action', distinct_id='distinct_id_of_the_user') -``` -```python -# Set event properties -posthog.capture( - "user_signed_up", - distinct_id="distinct_id_of_the_user", - properties={ - "login_type": "email", - "is_free_trial": "true" - } -) -``` -```python -# Page view event -posthog.capture('$pageview', distinct_id="distinct_id_of_the_user", properties={'$current_url': 'https://example.com'}) -``` - -## Capture AI - -### `Client.capture_ai` - -```python -capture_ai(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] -``` - -Capture an AI event on the dedicated AI capture endpoint. - -Beta: the signature is stable; operational limits (per-event size -cap, batching, endpoint) may change without notice. - -Takes the same arguments and returns the same value as `capture()`: -the event UUID, or None when the event was not admitted (disabled -client, or dropped by `before_send`). The event is queued on an -isolated AI lane with its own consumer pool and a higher per-event -size cap, posting to the dedicated AI ingestion endpoint. The payload -is sent as given — no redaction or truncation is applied here. - -## Capture exception - -### `Client.capture_exception` - -```python -capture_exception(exception: Optional[ExceptionArg], **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] -``` - -Capture an exception for error tracking. - -When OpenTelemetry is installed and a valid span is active, its trace and -span IDs are added as `$trace_id` and `$span_id` event properties. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`exception` | `Optional[ExceptionArg]` | The exception to capture. | *required* -`distinct_id` | | The distinct ID of the user. | -`properties` | | A dictionary of additional properties. Overriding reserved exception properties is deprecated and will stop working in the next major version. | -`flags` | | A `FeatureFlagEvaluations` snapshot from `evaluate_flags()`. Attaches those exact flag values to the captured `$exception` event. | -`send_feature_flags` | | Deprecated. Pass `flags` from `evaluate_flags()` instead. | -`disable_geoip` | | Whether to disable GeoIP for this event. | - -**Examples:** - -```python -try: - # Some code that might fail - pass -except Exception as e: - posthog.capture_exception(e, 'user_distinct_id', properties=additional_properties) -``` - -## Emails - -### `Client.emails` - -```python -emails: Emails -``` - -Send transactional email from PostHog email templates, and read the -status of each email sent with `get`. - -Needs `secret_key`, a project secret API key with the `hog_flow:write` -scope. Sends go to `app_host`, or to the PostHog Cloud app host of -`host`. `email_request_timeout_seconds` and `email_request_max_retries` -configure each send. A disabled or shut-down client sends nothing. See -`posthog.emails` for setup. - -**Examples:** - -```python -client.emails.send( - to="user@example.com", - template_id="0190f6d2-7c1e-7b3a-9f5e-1c2d3e4f5a6b", - template_data={"name": "Ada"}, - distinct_id="user_123", -) -``` - -## Flush - -### `Client.flush` - -```python -flush(timeout_seconds: Optional[float] = 10) -> None -``` - -Attempt immediate delivery of queued events, ended spans and autocaptured metrics, keeping the client reusable. - -For a client shared across requests or serverless invocations (including -the module-level client), call `flush()` before a short-lived invocation -returns. Use `shutdown()` instead only for final process cleanup or when -disposing of a client that will not be reused. Flush does not stop the -background workers or feature flag poller, and does not end open spans. - -Returning does not guarantee server receipt: pending work can remain after -a timeout or span export failure, and failed events may be dropped by the -consumers. Calls made directly from SDK callbacks such as `on_error` are -deferred rather than blocking the worker that invoked the callback. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`timeout_seconds` | `Optional[float]` | Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass `None` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently. The span exporter waits up to this many seconds for an in-flight flush, then uses a fresh drain budget once it acquires the lock. If the lock wait times out, the in-flight flush is left to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. Each span request is bounded by the client's `timeout` in seconds, and the last request is not cut short. This is therefore not a strict wall-clock limit for the whole call. With `None`, a retriable span failure is left for a later flush rather than retried within this call. | `10` - -**Examples:** - -```python -posthog.capture('event_name', distinct_id='user_id') -# Before a serverless invocation returns; keep the shared client open. -posthog.flush(timeout_seconds=10) -``` - -## Get feature flag - -### `Client.get_feature_flag` - -```python -get_feature_flag(key: str, distinct_id: ID_TYPES, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[FlagValue] -``` - -Get multivariate feature flag value for a user. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The feature flag key. | *required* -`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` -`only_evaluate_locally` | `bool` | Whether to only evaluate locally. | `False` -`send_feature_flag_events` | `bool` | Whether to send feature flag events. | `True` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` -`device_id` | `Optional[str]` | The device ID for this request. | `None` - -**Examples:** - -```python -enabled_variant = posthog.get_feature_flag('flag-key', 'distinct_id_of_your_user') -if enabled_variant == 'variant-key': # replace 'variant-key' with the key of your variant - # Do something differently for this user - # Optional: fetch the payload - matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user') -``` - -## Get feature flag payload - -### `Client.get_feature_flag_payload` - -```python -get_feature_flag_payload(key: str, distinct_id: ID_TYPES, *, match_value: Optional[FlagValue] = None, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = False, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[object] -``` - -Get the payload for a feature flag. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The feature flag key. | *required* -`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* -`match_value` | `Optional[FlagValue]` | The specific flag value to get payload for. | `None` -`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` -`only_evaluate_locally` | `bool` | Whether to only evaluate locally. | `False` -`send_feature_flag_events` | `bool` | Deprecated. Use get_feature_flag() instead if you need events. | `False` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` -`device_id` | `Optional[str]` | The device ID for this request. | `None` - -**Returns:** - -Type | Description ----- | ----------- -`Optional[object]` | The payload associated with the matched feature flag value, or None. -`Optional[object]` | This method returns the payload only, not the FeatureFlagResult wrapper -`Optional[object]` | used internally to compute it. - -**Examples:** - -```python -is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user') - -if is_my_flag_enabled: - # Do something differently for this user - # Optional: fetch the payload - matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user') -``` - -## Get feature flag result - -### `Client.get_feature_flag_result` - -```python -get_feature_flag_result(key: str, distinct_id: ID_TYPES, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[FeatureFlagResult] -``` - -Get a FeatureFlagResult object which contains the flag result and payload for a key by evaluating locally or remotely -depending on whether local evaluation is enabled and the flag can be locally evaluated. -This also captures the `$feature_flag_called` event unless `send_feature_flag_events` is `False`. - -**Examples:** - -```python -flag_result = posthog.get_feature_flag_result('flag-key', 'distinct_id_of_your_user') -if flag_result and flag_result.get_value() == 'variant-key': - # Do something differently for this user - # Optional: fetch the payload - matched_flag_payload = flag_result.payload -``` - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The feature flag key. | *required* -`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` -`only_evaluate_locally` | `bool` | Whether to only evaluate locally. | `False` -`send_feature_flag_events` | `bool` | Whether to send feature flag events. | `True` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` -`device_id` | `Optional[str]` | The device ID for this request. | `None` - -**Returns:** - -Type | Description ----- | ----------- -`Optional[FeatureFlagResult]` | Optional[FeatureFlagResult]: The feature flag result or None if disabled/not found. - -## Get feature flags - -### `Client.get_feature_variants` - -```python -get_feature_variants(distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, disable_geoip: Optional[bool] = None, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None) -> dict[str, Union[bool, str]] -``` - -Get feature flag variants for a user. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` -`flag_keys_to_evaluate` | `Optional[list[str]]` | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | `None` -`device_id` | `Optional[str]` | The device ID for this request. | `None` - -### `Client.get_all_flags` - -```python -get_all_flags(distinct_id: ID_TYPES, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None) -> Optional[dict[str, Union[bool, str]]] -``` - -Get all feature flags for a user. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` -`only_evaluate_locally` | `bool` | Whether to only evaluate locally. | `False` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` -`flag_keys_to_evaluate` | `Optional[list[str]]` | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | `None` -`device_id` | `Optional[str]` | The device ID for this request. | `None` - -**Examples:** - -```python -posthog.get_all_flags('distinct_id_of_your_user') -``` - -## Get feature flags and payloads - -### `Client.get_feature_flags_and_payloads` - -```python -get_feature_flags_and_payloads(distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, disable_geoip: Optional[bool] = None, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None) -> FlagsAndPayloads -``` - -Get feature flags and payloads for a user. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` -`flag_keys_to_evaluate` | `Optional[list[str]]` | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | `None` -`device_id` | `Optional[str]` | The device ID for this request. | `None` - -**Examples:** - -```python -result = posthog.get_feature_flags_and_payloads('') -``` - -### `Client.get_all_flags_and_payloads` - -```python -get_all_flags_and_payloads(distinct_id: ID_TYPES, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None) -> FlagsAndPayloads -``` - -Get all feature flags and their payloads for a user. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` -`only_evaluate_locally` | `bool` | Whether to only evaluate locally. | `False` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` -`flag_keys_to_evaluate` | `Optional[list[str]]` | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | `None` -`device_id` | `Optional[str]` | The device ID for this request. | `None` - -**Examples:** - -```python -posthog.get_all_flags_and_payloads('distinct_id_of_your_user') -``` - -## Evaluate flags - -### `Client.evaluate_flags` - -```python -evaluate_flags(distinct_id: Optional[ID_TYPES] = None, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, flag_keys: Optional[List[str]] = None, device_id: Optional[str] = None) -> FeatureFlagEvaluations -``` - -Evaluate all feature flags for a user in a single call and return a -`FeatureFlagEvaluations` snapshot. Branch on `.is_enabled()` / -`.get_flag()` and pass the same snapshot to `capture` via the -`flags` option so events carry the exact flag values the code branched on. - -Prefer this over repeated `get_feature_flag()` calls and over -`capture(send_feature_flags=True)` — it consolidates flag evaluation into -a single `/flags` request per incoming request. - -Local evaluation is transparent: when the poller resolves a flag, the -snapshot's `$feature_flag_called` events are tagged `locally_evaluated=True` -and reason `"Evaluated locally"`. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | `Optional[ID_TYPES]` | The user's distinct ID. If `None`, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot. | `None` -`groups` | `Optional[Mapping[str, Union[str, int]]]` | Mapping of group type to group key. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | Person properties to use for evaluation. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties keyed by group type. | `None` -`only_evaluate_locally` | `bool` | If True, never fall back to remote evaluation — flags that can't be evaluated locally are omitted from the snapshot and listed with a reason in its `unresolved_flags`. | `False` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup. | `None` -`flag_keys` | `Optional[List[str]]` | Optional list that scopes local evaluation, the underlying `/flags` request, and the returned snapshot. When omitted or `None`, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per `evaluate_flags` call unless `only_evaluate_locally` is True. If the server also does not know the key, it is omitted from the snapshot. | `None` -`device_id` | `Optional[str]` | Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes. | `None` - -**Returns:** - -Type | Description ----- | ----------- -`FeatureFlagEvaluations` | A `FeatureFlagEvaluations` snapshot. - -**Examples:** - -```python -flags = posthog.evaluate_flags( - "user_123", - person_properties={"plan": "enterprise"}, -) -if flags.is_enabled("new-dashboard"): - render_new_dashboard() -posthog.capture("page_viewed", distinct_id="user_123", flags=flags) -``` - -## Group identify - -### `Client.group_identify` - -```python -group_identify(group_type: str, group_key: str, properties: Optional[Dict[str, Any]] = None, timestamp: Optional[Union[datetime, str]] = None, uuid: Optional[Union[str, UUID]] = None, disable_geoip: Optional[bool] = None, distinct_id: Optional[ID_TYPES] = None) -> Optional[str] -``` - -Identify a group and set its properties. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`group_type` | `str` | The type of group (e.g., 'company', 'team'). Required - the call is dropped with a warning if it is missing or empty. | *required* -`group_key` | `str` | The unique identifier for the group. Required - the call is dropped with a warning if it is missing or empty. | *required* -`properties` | `Optional[Dict[str, Any]]` | A dictionary of properties to set on the group. | `None` -`timestamp` | `Optional[Union[datetime, str]]` | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | `None` -`uuid` | `Optional[Union[str, UUID]]` | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | `None` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this event. | `None` -`distinct_id` | `Optional[ID_TYPES]` | The distinct ID of the user performing the action. | `None` - -**Examples:** - -```python -posthog.group_identify('company', 'company_id_in_your_db', { - 'name': 'Awesome Inc.', - 'employees': 11 -}) -``` - -Note: This method will not raise exceptions. Errors are logged. - -## Identify - -### `Client.set` - -```python -set(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str] -``` - -Set properties on a person profile. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | | The distinct ID of the user. | -`properties` | | A dictionary of properties to set. | -`timestamp` | | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | -`uuid` | | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | -`disable_geoip` | | Whether to disable GeoIP for this event. | - -**Examples:** - -```python -# Set with distinct id -posthog.set(distinct_id='user123', properties={'name': 'Max Hedgehog'}) -``` - -Note: This method will not raise exceptions. Errors are logged. - -### `Client.set_once` - -```python -set_once(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str] -``` - -Set properties on a person profile only if they haven't been set before. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | | The distinct ID of the user. | -`properties` | | A dictionary of properties to set once. | -`timestamp` | | The timestamp of the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | -`uuid` | | A unique identifier for the event. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | -`disable_geoip` | | Whether to disable GeoIP for this event. | - -**Examples:** - -```python -posthog.set_once(distinct_id='user123', properties={'initial_signup_date': '2024-01-01'}) -``` - -Note: This method will not raise exceptions. Errors are logged. - -## Is feature enabled - -### `Client.feature_enabled` - -```python -feature_enabled(key: str, distinct_id: ID_TYPES, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[bool] -``` - -Check if a feature flag is enabled for a user. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The feature flag key. | *required* -`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` -`only_evaluate_locally` | `bool` | Whether to only evaluate locally. | `False` -`send_feature_flag_events` | `bool` | Whether to send feature flag events. | `True` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` -`device_id` | `Optional[str]` | The device ID for this request. | `None` - -**Examples:** - -```python -is_my_flag_enabled = posthog.feature_enabled('flag-key', 'distinct_id_of_your_user') -if is_my_flag_enabled: - # Do something differently for this user - # Optional: fetch the payload - matched_flag_payload = posthog.get_feature_flag_payload('flag-key', 'distinct_id_of_your_user') -``` - -## Setup - -### `Client(...)` - -```python -__init__(project_api_key: str, host: str = None, debug: str = False, max_queue_size: str = 10000, send: str = True, on_error: str = None, flush_at: str = 100, flush_interval: str = 5.0, gzip: str = False, max_retries: str = 3, sync_mode: str = False, timeout: float = 15, thread: float = 1, poll_interval: float = 30, personal_api_key: float = None, disabled: float = False, disable_geoip: float = True, is_server: float = True, historical_migration: float = False, feature_flags_request_timeout_seconds: float = 3, feature_flags_request_max_retries: float = 1, super_properties: float = None, enable_exception_autocapture: float = False, log_captured_exceptions: float = False, project_root: float = None, privacy_mode: float = False, before_send: float = None, flag_fallback_cache_url: float = None, enable_local_evaluation: float = True, flag_definition_cache_provider: Optional[FlagDefinitionCacheProvider] = None, capture_exception_code_variables: Optional[FlagDefinitionCacheProvider] = False, code_variables_mask_patterns: Optional[FlagDefinitionCacheProvider] = None, code_variables_ignore_patterns: Optional[FlagDefinitionCacheProvider] = None, code_variables_mask_url_credentials: Optional[FlagDefinitionCacheProvider] = None, code_variables_detect_secrets: Optional[FlagDefinitionCacheProvider] = None, in_app_modules: list[str] | None = None, enable_exception_autocapture_rate_limiting: list[str] | None = False, exception_autocapture_bucket_size: list[str] | None = ExceptionCapture.DEFAULT_BUCKET_SIZE, exception_autocapture_refill_rate: list[str] | None = ExceptionCapture.DEFAULT_REFILL_RATE, exception_autocapture_refill_interval_seconds: list[str] | None = ExceptionCapture.DEFAULT_REFILL_INTERVAL_SECONDS, capture_mode: Optional[Union[CaptureMode, str]] = None, capture_compression: Optional[Union[CaptureCompression, str]] = None, secret_key: Optional[Union[CaptureCompression, str]] = None, metrics: Optional[dict] = None, enable_full_ai_capture: Optional[dict] = False, capture_trace_context: Optional[dict] = False, _use_ai_lane: Optional[dict] = False, _enable_multimodal_capture: Optional[dict] = False, traces: Optional[dict] = None, app_host: Optional[str] = None, email_request_timeout_seconds: float = 15, email_request_max_retries: int = 2) -``` - -Initialize a new PostHog client instance. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`project_api_key` | `str` | PostHog project API key/token. | *required* -`host` | | PostHog host. Defaults to the US ingestion endpoint when not set. App hosts such as `https://us.posthog.com` are mapped to the corresponding ingestion host. | `None` -`debug` | | Enable verbose SDK logging and re-raise errors from public API methods. | `False` -`max_queue_size` | | Maximum number of events buffered before upload. | `10000` -`send` | | If False, queueing succeeds but events are not sent. | `True` -`on_error` | | Optional callback invoked by background consumers when an upload fails. Keep it short and non-blocking. Calling lifecycle methods directly is safe and deferred, but do not start another thread or task that calls `flush()`, `join()`, or `shutdown()` and then wait for it from the callback. | `None` -`flush_at` | | Number of queued events that triggers a batch upload. | `100` -`flush_interval` | | Maximum seconds a background consumer waits before flushing a partial batch. | `5.0` -`gzip` | | Whether to gzip event upload payloads. | `False` -`max_retries` | | Number of upload retries. Values below 0 are treated as 0. | `3` -`sync_mode` | | If True, send each event synchronously instead of using background worker threads. This blocks the calling thread; in asyncio applications such as FastAPI, use `AsyncPosthog` instead. | `False` -`timeout` | `float` | HTTP request timeout in seconds for event uploads. | `15` -`thread` | | Number of background consumer threads. | `1` -`poll_interval` | | Seconds between local feature flag definition refreshes. | `30` -`secret_key` | | A Personal API Key or Project Secret API Key, used to authenticate local feature flag evaluation, remote config payloads, and decrypted flag payloads. Example::
posthog.Client(project_api_key, secret_key="phx_...") | `None` -`personal_api_key` | | Deprecated alias for `secret_key`. Still honored for backwards compatibility; prefer `secret_key`, which also accepts a Project Secret API Key. | `None` -`disabled` | | If True, disable captures and API requests. Useful in tests. | `False` -`disable_geoip` | | Whether to disable server-side GeoIP enrichment. Defaults to True. | `True` -`is_server` | | Whether events are emitted from a server-side runtime. Defaults to True; set to False when using the SDK as a client/CLI so the device OS is attributed to the person normally. | `True` -`historical_migration` | | Mark events as historical migration imports. | `False` -`feature_flags_request_timeout_seconds` | `float` | Timeout in seconds for feature flag and remote config requests. | `3` -`feature_flags_request_max_retries` | | Number of retries for feature flag requests after network, transport, or timeout failures. Defaults to 1. Set to 0 to disable retries. | `1` -`app_host` | `Optional[str]` | Your PostHog app host, such as `https://posthog.example.com`. `emails` sends there. Needed only when `host` is not PostHog Cloud, for example a reverse proxy, which must not receive the secret key. | `None` -`email_request_timeout_seconds` | `float` | Timeout in seconds for each email send request. Defaults to 15. | `15` -`email_request_max_retries` | `int` | Number of retries for an email send after a network error, a rate limit, or an unavailable service. Defaults to 2. Set to 0 when a job runner already retries the send. | `2` -`super_properties` | | Properties merged into every captured event. | `None` -`enable_exception_autocapture` | | Automatically capture uncaught exceptions. | `False` -`log_captured_exceptions` | | Also log exceptions captured by error tracking. | `False` -`project_root` | | Root path used to determine in-app stack frames for captured exceptions. Defaults to the current working directory. | `None` -`privacy_mode` | | For AI observability, capture usage metadata without prompt inputs or outputs. | `False` -`enable_full_ai_capture` | | Route PostHog AI wrapper events through the dedicated AI capture endpoint and capture full AI content: skips string truncation and passes media (base64/data URIs) through unredacted. `privacy_mode` always wins. Defaults to False. | `False` -`before_send` | | Optional callback that can modify or drop events before upload. Return `None` to drop an event. | `None` -`flag_fallback_cache_url` | | Optional feature flag fallback cache URL, such as `memory://local/?ttl=300&size=10000` or a Redis URL. | `None` -`enable_local_evaluation` | | Whether to poll feature flag definitions for local evaluation when a personal API key is configured. | `True` -`flag_definition_cache_provider` | `Optional[FlagDefinitionCacheProvider]` | Optional external cache provider for sharing feature flag definitions across workers. | `None` -`capture_exception_code_variables` | | Capture local variable values on exception stack frames. | `False` -`capture_trace_context` | | When OpenTelemetry is installed and a valid span is active at capture time, add its trace and span IDs as `$trace_id` and `$span_id` properties to events captured with `capture()` and `capture_ai()`, so they can be correlated with backend traces. Explicit `$trace_id`/`$span_id` values passed in `properties` win. Exception events (`capture_exception`) always attach these IDs regardless of this setting. Defaults to False. | `False` -`traces` | `Optional[dict]` | Config dict for distributed tracing: `service_name`, `service_version`, `environment`, `resource_attributes`, `flush_interval` (5 s), `max_queue_size` (2048), `max_export_batch_size` (512), `max_live_spans` (10000), `max_span_age` (3600 s), `max_attributes_per_span` (128), `max_events_per_span` (128), `max_attribute_value_length` (8192). `before_span_send` is a callable, or a list run in order, that receives each finished span as a dict (`trace_id`, `span_id` and `parent_span_id` are read-only) and returns it, edited, or `None` to drop it; a hook that raises drops the span. Tracing is off until this is provided. Spans export on a background timer even with `sync_mode`; serverless handlers should call `flush()` before returning. Defaults to None. | `None` -`code_variables_mask_patterns` | | Variable-name patterns to mask when capturing code variables. | `None` -`code_variables_ignore_patterns` | | Variable-name patterns to omit when capturing code variables. | `None` -`code_variables_mask_url_credentials` | | Scrub credentials embedded in URLs/DSNs (e.g. `user:pass@host`) from captured code variables, regardless of the surrounding variable name. Defaults to True. | `None` -`code_variables_detect_secrets` | | Last-resort entropy-based detection that redacts high-entropy secret-looking values (API keys, tokens, strong passwords) sitting in innocuously-named variables, after the name and URL checks. Skips structured ids (UUIDs, ObjectIds, hashes). Defaults to True. | `None` -`in_app_modules` | `list[str] \| None` | Module/package prefixes treated as in-app frames in captured exceptions. | `None` -`enable_exception_autocapture_rate_limiting` | | Rate limit autocaptured exceptions client-side with a token bucket per exception type. Disabled by default. | `False` -`exception_autocapture_bucket_size` | | Maximum burst of autocaptured exceptions allowed per exception type (token bucket size, clamped to 0-100). | `DEFAULT_BUCKET_SIZE` -`exception_autocapture_refill_rate` | | Tokens restored per refill interval for each exception type's bucket. | `DEFAULT_REFILL_RATE` -`exception_autocapture_refill_interval_seconds` | | Seconds between token refills for autocaptured exception rate limiting. | `DEFAULT_REFILL_INTERVAL_SECONDS` -`capture_mode` | `Optional[Union[CaptureMode, str]]` | Capture wire protocol to use. Defaults to `CaptureMode.V0` (legacy `/batch/`). Set `CaptureMode.V1` (or pass the string `"v1"`) to opt into `/i/v1/analytics/events`. When omitted, the `POSTHOG_CAPTURE_MODE` env var is consulted, then `V0`. | `None` -`capture_compression` | `Optional[Union[CaptureCompression, str]]` | Request-body compression for capture-v1 uploads (ignored in V0, which uses `gzip`). `CaptureCompression.GZIP` or `DEFLATE` (or the strings `"gzip"`/`"deflate"`). When omitted, the `POSTHOG_CAPTURE_COMPRESSION` env var is consulted, then the legacy `gzip` flag, then no compression. | `None` - -**Examples:** - -```python -from posthog import Posthog - -posthog = Posthog('', host='') -``` - -## Shutdown - -### `Client.join` - -```python -join() -> None -``` - -Attempt to process queued events and end the consumer threads. Do not use directly, call `shutdown()` instead. - -Failed or undrainable events may be dropped and reported through logging -or `on_error`; returning does not guarantee server receipt. Lifecycle -cleanup is attempted once, and cleanup failures are logged without retry. - -**Examples:** - -```python -posthog.join() -``` - -### `Client.shutdown` - -```python -shutdown() -> None -``` - -Attempt final delivery and permanently shut down the client. - -Call this for final process cleanup or when disposing of a client that -will not be reused. It closes capture lanes, stops background workers and -the feature flag poller, and tears down integrations. Do not call it at the -end of each request or serverless invocation when the client is shared; -call `flush()` before returning instead. A shut-down client cannot be -reused; create a new client if needed. - -Normally this method blocks until queued events have been attempted and -cleanup finishes. Failed or undrainable events may be dropped and -reported through logging or `on_error`; returning does not guarantee -server receipt. Event draining has no timeout. Queued spans get one final -flush with up to 30 seconds waiting for an in-flight flush, then a fresh -30-second drain budget; the last HTTP request is not cut short. Any spans -it cannot send are discarded with a warning, as are spans still open. -Lifecycle cleanup is attempted once, and cleanup failures -are logged without retry. When called directly from an SDK callback such as -`on_error`, shutdown is deferred to avoid blocking the worker that -invoked the callback. If the callback must coordinate a blocking -shutdown, have it signal an -application-owned thread and return before that thread calls shutdown. -Do not wait inside the callback for another thread or task that calls a -lifecycle method. - -**Examples:** - -```python -posthog.shutdown() -``` - -## Flag definition loader - -### `Client.load_feature_flags` - -```python -load_feature_flags() -``` - -Load feature flags for local evaluation. - -**Examples:** - -```python -posthog.load_feature_flags() -``` - -### `Client.feature_flag_definitions` - -```python -feature_flag_definitions() -``` - -Return feature flag definitions loaded for local evaluation. - -**Returns:** - -Type | Description ----- | ----------- - | The currently loaded feature flag definitions, or `None` before - | local evaluation has loaded definitions. - -## SDK utils - -### `Client.queue` - -```python -queue: Queue -``` - -The analytics lane's queue (kept for backwards compatibility). - -### `Client.consumers` - -```python -consumers: Optional[List[Consumer]] -``` - -Flat list of the lanes' consumers, analytics first (kept for backwards compatibility). - -### `Client.new_context` - -```python -new_context(fresh = False, capture_exceptions: Optional[bool] = None) -``` - -Create a new context for managing shared state. Learn more about [contexts](/docs/libraries/python#contexts). - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`fresh` | | Whether to create a fresh context that doesn't inherit from parent. | `False` -`capture_exceptions` | `Optional[bool]` | Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting. | `None` - -**Examples:** - -```python -with client.new_context(): - client.identify_context('') - client.capture('event_name') -``` - -
-Category - -Contexts - -
- -### `Client.scoped` - -```python -scoped(fresh = False, capture_exceptions: Optional[bool] = None) -``` - -Decorator that creates a new context for the wrapped function using this client. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`fresh` | | Whether to create a fresh context that doesn't inherit from parent. | `False` -`capture_exceptions` | `Optional[bool]` | Whether to automatically capture exceptions in this context. If omitted, defaults to this client's exception autocapture setting. | `None` - -
-Category - -Contexts - -
- -### `Client.tag` - -```python -tag(name: str, value: Any) -> None -``` - -Add a tag to the current context. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`name` | `str` | The tag key. | *required* -`value` | `Any` | The tag value. | *required* - -
-Category - -Contexts - -
- -### `Client.get_tags` - -```python -get_tags() -> Dict[str, Any] -``` - -Get all tags from the current context. - -**Returns:** - -Type | Description ----- | ----------- -`Dict[str, Any]` | Dict of all tags in the current context. - -
-Category - -Contexts - -
- -### `Client.identify_context` - -```python -identify_context(distinct_id: str) -> None -``` - -Identify the current context with a distinct ID. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | `str` | The distinct ID to associate with the current context and its children. | *required* - -
-Category - -Contexts - -
- -### `Client.set_context_session` - -```python -set_context_session(session_id: str) -> None -``` - -Set the session ID for the current context. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`session_id` | `str` | The session ID to associate with the current context and its children. | *required* - -
-Category - -Contexts - -
- -### `Client.set_context_device_id` - -```python -set_context_device_id(device_id: str) -> None -``` - -Set the device ID for the current context. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`device_id` | `str` | The device ID to associate with the current context and its children. | *required* - -
-Category - -Contexts - -
- -### `Client.feature_flags` - -```python -feature_flags -``` - -Get the local evaluation feature flags. - -### `Client.get_feature_payloads` - -```python -get_feature_payloads(distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, disable_geoip: Optional[bool] = None, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None) -> dict[str, Optional[str]] -``` - -Get feature flag payloads for a user, preserving valid serialized JSON. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | `ID_TYPES` | The distinct ID of the user. | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` -`flag_keys_to_evaluate` | `Optional[list[str]]` | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | `None` -`device_id` | `Optional[str]` | The device ID for this request. | `None` - -**Examples:** - -```python -payloads = posthog.get_feature_payloads('') -``` - -
-Category - -Feature flags - -
- -### `Client.get_flags_decision` - -```python -get_flags_decision(distinct_id: Optional[ID_TYPES] = None, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, disable_geoip: Optional[bool] = None, flag_keys_to_evaluate: Optional[list[str]] = None, device_id: Optional[str] = None) -> FlagsResponse -``` - -Get feature flags decision. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | `Optional[ID_TYPES]` | The distinct ID of the user. | `None` -`groups` | `Optional[Mapping[str, Union[str, int]]]` | A dictionary of group information. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | A dictionary of person properties. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | A dictionary of group properties. | `None` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP for this request. | `None` -`flag_keys_to_evaluate` | `Optional[list[str]]` | A list of specific flag keys to evaluate. If provided, only these flags will be evaluated, improving performance. | `None` -`device_id` | `Optional[str]` | The device ID for this request. | `None` - -**Examples:** - -```python -decision = posthog.get_flags_decision('user123') -``` - -
-Category - -Feature flags - -
- -### `Client.metrics` - -```python -metrics: PostHogMetrics -``` - -The `posthog.metrics` API: a statsd-style pre-aggregating metrics client — alpha. - -Samples fold into per-series aggregates in memory and flush as one OTLP -data point per series per window, so recording from hot paths is cheap. -Configure via the `metrics` client option; pending metrics flush on -`shutdown()`. - -**Examples:** - -```python -client = Posthog("", metrics={"service_name": "billing-worker"}) -client.metrics.count("invoices.processed", 1, attributes={"plan": "pro"}) -client.metrics.gauge("queue.depth", 42) -client.metrics.histogram("job.duration", 187, unit="ms") -``` - -### `Client.get_remote_config_payload` - -```python -get_remote_config_payload(key: str) -``` - -Get the payload for a remote config feature flag. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The remote config feature flag key. | *required* - -**Returns:** - -Type | Description ----- | ----------- - | The payload associated with the feature flag, or `None` if the - | client is disabled, no personal API key is configured, or the request - | fails. Encrypted payloads are decrypted by PostHog before being - | returned. - -
-Note - -Requires `secret_key` for authentication. - -
- -
-Category - -Feature flags - -
- -### `Client.get_feature_flag_evaluation_runtime` - -```python -get_feature_flag_evaluation_runtime(key: str) -> Optional[FeatureFlagEvaluationRuntime] -``` - -Return where a locally loaded feature flag is meant to be evaluated. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The feature flag key. | *required* - -**Returns:** - -Type | Description ----- | ----------- -`Optional[FeatureFlagEvaluationRuntime]` | The flag's evaluation runtime, or `None` when local evaluation has -`Optional[FeatureFlagEvaluationRuntime]` | not loaded a definition for this key. A definition that carries no -`Optional[FeatureFlagEvaluationRuntime]` | runtime reports `FeatureFlagEvaluationRuntime.ALL`, the default -`Optional[FeatureFlagEvaluationRuntime]` | PostHog applies. - -**Examples:** - -```python -from posthog import FeatureFlagEvaluationRuntime - -runtime = posthog.get_feature_flag_evaluation_runtime("my-flag") -if runtime is FeatureFlagEvaluationRuntime.SERVER: - ... -``` - -
-Category - -Feature flags - -
- -### `Client.get_feature_flag_keys_by_evaluation_runtime` - -```python -get_feature_flag_keys_by_evaluation_runtime(evaluation_runtime: Union[FeatureFlagEvaluationRuntime, str]) -> list[str] -``` - -Return the keys of locally loaded flags that a runtime can evaluate. - -A flag set to `FeatureFlagEvaluationRuntime.ALL` suits either runtime, -so it is returned for `CLIENT` and for `SERVER`, and asking for -`ALL` returns every loaded flag. Use this to decide which flags to hand -to a browser when a backend serves flags to its own frontend. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`evaluation_runtime` | `Union[FeatureFlagEvaluationRuntime, str]` | The runtime to match, as a `FeatureFlagEvaluationRuntime` or its string value. | *required* - -**Returns:** - -Type | Description ----- | ----------- -`list[str]` | The matching flag keys, in the order local evaluation loaded them. -`list[str]` | Empty when no definitions are loaded. - -**Examples:** - -```python -from posthog import FeatureFlagEvaluationRuntime - -client_keys = posthog.get_feature_flag_keys_by_evaluation_runtime( - FeatureFlagEvaluationRuntime.CLIENT -) -``` - -
-Category - -Feature flags - -
- -### `posthog.integrations.django.PosthogContextMiddleware` - -```python -PosthogContextMiddleware(get_response) -``` - -Middleware to automatically track Django requests. - -This middleware wraps all calls with a posthog context. It attempts to extract the following from the request: -- Session ID, (extracted from `X-POSTHOG-SESSION-ID`) -- Distinct ID, (extracted from `X-POSTHOG-DISTINCT-ID`, falling back to the authenticated request user ID) -- Authenticated user email as `email` -- Request URL as `$current_url` -- Request method as `$request_method` -- Request path as `$request_path` -- Forwarded IP address as `$ip` -- User agent as `$user_agent` - -Configure the integration with these Django settings: -- `POSTHOG_MW_CLIENT`: client used for automatic exception capture, or the global client. - This does not redirect events explicitly captured through other clients. -- `POSTHOG_MW_CAPTURE_EXCEPTIONS`: `False` enables context enrichment only; - `True` enables automatic exception capture; `None` inherits the effective client's - `enable_exception_autocapture`. When omitted, the legacy default is `True`. -- `POSTHOG_MW_REQUEST_FILTER`: a callback receiving the request. Returning `False` - disables both enrichment and automatic exception capture for that request. -- `POSTHOG_MW_EXTRA_PROPERTIES`: a callback returning additional event properties. -- `POSTHOG_MW_CAPTURE_TRACING_HEADERS`: read PostHog identity and session headers - as request-scoped analytics context (default: `True`). Explicit capture IDs and - event properties take precedence. These values are never authentication or - authorization. Authenticated Django user identity remains the fallback when no - usable header ID is available or header capture is disabled. -- `POSTHOG_MW_TRUST_TRACING_HEADERS`: deprecated alias for - `POSTHOG_MW_CAPTURE_TRACING_HEADERS`; the capture-named setting takes precedence. -- `POSTHOG_MW_PROPERTIES_MAP`: an optional callback transforming request properties. - -The old `POSTHOG_MW_EXTRA_TAGS` and `POSTHOG_MW_TAG_MAP` settings remain aliases; -the property-named settings take precedence when both are configured. - -Request properties are included on all events captured within the active context, -including when automatic exception capture is disabled. -See the context documentation for more information. The extracted distinct ID and session ID, -if found, are used to associate all events captured in the middleware context with the same distinct ID -and session as currently active on the frontend. See the documentation for `set_context_session` -and `identify_context` for more details. - -This middleware is hybrid-capable: it supports both WSGI (sync) and ASGI (async) Django applications. The middleware -detects at initialization whether the next middleware in the chain is async or sync, and adapts its behavior accordingly. -This ensures compatibility with both pure sync and pure async middleware chains, as well as mixed chains in ASGI mode. - -**Functions:** - -Name | Description ----- | ----------- -`aextract_properties` | Async version of extract_properties for use in async request handling. -`aextract_request_user` | Async version of extract_request_user for use in async request handling. -`aextract_tags` | Compatibility alias for `aextract_properties`. -`extract_properties` | Extract event properties and identity from a synchronous request. -`extract_request_user` | Extract user ID and email from request in sync context. -`extract_tags` | Compatibility alias for `extract_properties`. -`process_exception` | Process exceptions from views and downstream middleware. - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`async_capable` | | -`capture_exceptions` | `Optional[bool]` | -`capture_tracing_headers` | | -`client` | | -`extra_properties` | | -`extra_tags` | | Compatibility alias for `extra_properties`. -`get_response` | | -`properties_map` | | -`request_filter` | | -`sync_capable` | | -`tag_map` | | Compatibility alias for `properties_map`. -`trust_tracing_headers` | `bool` | Deprecated alias for `capture_tracing_headers`. - -Initialize the middleware with Django's next handler. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`get_response` | | The next middleware or view handler in Django's middleware chain. May be synchronous or asynchronous. | - -#### `posthog.integrations.django.PosthogContextMiddleware.aextract_properties` - -```python -aextract_properties(request) -``` - -Async version of extract_properties for use in async request handling. - -Uses await request.auser() instead of request.user to avoid -SynchronousOnlyOperation in async context. - -Follows Django's naming convention for async methods (auser, asave, etc.). - -#### `posthog.integrations.django.PosthogContextMiddleware.aextract_request_user` - -```python -aextract_request_user(request) -``` - -Async version of extract_request_user for use in async request handling. - -Uses await request.auser() instead of request.user to avoid -SynchronousOnlyOperation in async context. - -Follows Django's naming convention for async methods (auser, asave, etc.). - -#### `posthog.integrations.django.PosthogContextMiddleware.aextract_tags` - -```python -aextract_tags(request) -``` - -Compatibility alias for `aextract_properties`. - -#### `posthog.integrations.django.PosthogContextMiddleware.async_capable` - -```python -async_capable = True -``` - -#### `posthog.integrations.django.PosthogContextMiddleware.capture_exceptions` - -```python -capture_exceptions: Optional[bool] = capture_exceptions if (capture_exceptions is None or isinstance(capture_exceptions, bool)) else True -``` - -#### `posthog.integrations.django.PosthogContextMiddleware.capture_tracing_headers` - -```python -capture_tracing_headers = _resolve_capture_tracing_headers(capture_tracing_headers, trust_tracing_headers, canonical_name='POSTHOG_MW_CAPTURE_TRACING_HEADERS', deprecated_name='POSTHOG_MW_TRUST_TRACING_HEADERS') -``` - -#### `posthog.integrations.django.PosthogContextMiddleware.client` - -```python -client = cast('Optional[Client]', settings.POSTHOG_MW_CLIENT) -``` - -#### `posthog.integrations.django.PosthogContextMiddleware.extra_properties` - -```python -extra_properties = cast('Callable[[HttpRequest], Dict[str, Any]]', extra_properties) if callable(extra_properties) else None -``` - -#### `posthog.integrations.django.PosthogContextMiddleware.extra_tags` - -```python -extra_tags -``` - -Compatibility alias for `extra_properties`. - -#### `posthog.integrations.django.PosthogContextMiddleware.extract_properties` - -```python -extract_properties(request) -``` - -Extract event properties and identity from a synchronous request. - -#### `posthog.integrations.django.PosthogContextMiddleware.extract_request_user` - -```python -extract_request_user(request) -``` - -Extract user ID and email from request in sync context. - -#### `posthog.integrations.django.PosthogContextMiddleware.extract_tags` - -```python -extract_tags(request) -``` - -Compatibility alias for `extract_properties`. - -#### `posthog.integrations.django.PosthogContextMiddleware.get_response` - -```python -get_response = get_response -``` - -#### `posthog.integrations.django.PosthogContextMiddleware.process_exception` - -```python -process_exception(request, exception) -``` - -Process exceptions from views and downstream middleware. - -Django calls this WHILE still inside the context created by __call__, -so request tags have already been extracted and set. This method just -needs to capture the exception directly. - -Django converts view exceptions into responses before they propagate through -the middleware stack, so the context manager in __call__/__acall__ never sees them. - -Note: Django's process_exception is always synchronous, even for async views. - -#### `posthog.integrations.django.PosthogContextMiddleware.properties_map` - -```python -properties_map = cast('Callable[[Dict[str, Any]], Dict[str, Any]]', properties_map) if callable(properties_map) else None -``` - -#### `posthog.integrations.django.PosthogContextMiddleware.request_filter` - -```python -request_filter = cast('Optional[Callable[[HttpRequest], bool]]', settings.POSTHOG_MW_REQUEST_FILTER) -``` - -#### `posthog.integrations.django.PosthogContextMiddleware.sync_capable` - -```python -sync_capable = True -``` - -#### `posthog.integrations.django.PosthogContextMiddleware.tag_map` - -```python -tag_map -``` - -Compatibility alias for `properties_map`. - -#### `posthog.integrations.django.PosthogContextMiddleware.trust_tracing_headers` - -```python -trust_tracing_headers: bool -``` - -Deprecated alias for `capture_tracing_headers`. - -### `posthog.integrations.flask.PosthogFlaskIntegration` - -```python -PosthogFlaskIntegration(app: Optional[Flask] = None, *, client: Optional[Client] = None, capture_exceptions: Optional[bool] = None, request_filter: Optional[Callable[[Request], bool]] = None, extra_properties: Optional[Callable[[Request], Mapping[str, Any]]] = None, trust_tracing_headers: Optional[bool] = None, capture_tracing_headers: Optional[bool] = None) -> None -``` - -Add PostHog request context and exception tracking to a Flask app. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`app` | `Optional[Flask]` | An optional Flask application. If omitted, call `init_app` later (the Flask application-factory pattern). | `None` -`client` | `Optional[Client]` | Optional destination for automatically captured exceptions. The global client is used by default. Request context still enriches events captured through any client and does not redirect capture calls. | `None` -`capture_exceptions` | `Optional[bool]` | Capture exceptions that reach Flask's unhandled exception machinery. If omitted, inherit the effective client's `enable_exception_autocapture` setting. Pass `True` or `False` to override it. | `None` -`request_filter` | `Optional[Callable[[Request], bool]]` | Optional callback receiving Flask's request object. A false return value disables both context and exception capture for that request. | `None` -`extra_properties` | `Optional[Callable[[Request], Mapping[str, Any]]]` | Optional callback returning additional event properties. This is useful for application-specific metadata such as an authenticated user's role. Values should not contain secrets or request bodies. | `None` -`capture_tracing_headers` | `Optional[bool]` | Read PostHog distinct and session ID headers as request-scoped analytics context. Defaults to True. Explicit capture IDs and event properties take precedence. Never use these values for security. | `None` -`trust_tracing_headers` | `Optional[bool]` | Deprecated alias for `capture_tracing_headers`. The capture-named option takes precedence when both are supplied. | `None` - -The integration intentionally does not capture exceptions handled by an -application error handler, expected HTTP exceptions, request or response -bodies, query strings, cookies, authorization headers, or arbitrary headers. -Call `capture_exception` explicitly from a custom error handler if a -handled exception should be reported. - -**Functions:** - -Name | Description ----- | ----------- -`init_app` | Register the integration with a Flask application once. - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`capture_exceptions` | | -`capture_tracing_headers` | | -`client` | | -`extra_properties` | | -`request_filter` | | -`trust_tracing_headers` | `bool` | Deprecated alias for `capture_tracing_headers`. - -#### `posthog.integrations.flask.PosthogFlaskIntegration.capture_exceptions` - -```python -capture_exceptions = capture_exceptions -``` - -#### `posthog.integrations.flask.PosthogFlaskIntegration.capture_tracing_headers` - -```python -capture_tracing_headers = _resolve_capture_tracing_headers(capture_tracing_headers, trust_tracing_headers) -``` - -#### `posthog.integrations.flask.PosthogFlaskIntegration.client` - -```python -client = client -``` - -#### `posthog.integrations.flask.PosthogFlaskIntegration.extra_properties` - -```python -extra_properties = extra_properties -``` - -#### `posthog.integrations.flask.PosthogFlaskIntegration.init_app` - -```python -init_app(app: Flask) -> None -``` - -Register the integration with a Flask application once. - -#### `posthog.integrations.flask.PosthogFlaskIntegration.request_filter` - -```python -request_filter = request_filter -``` - -#### `posthog.integrations.flask.PosthogFlaskIntegration.trust_tracing_headers` - -```python -trust_tracing_headers: bool -``` - -Deprecated alias for `capture_tracing_headers`. - -### `posthog.integrations.asgi.PosthogASGIMiddleware` - -```python -PosthogASGIMiddleware(app: _ASGIApp, client: Optional[Client] = None, capture_exceptions: Optional[bool] = None, request_filter: Optional[_RequestFilter] = None, extra_properties: Optional[_ExtraProperties] = None, trust_tracing_headers: Optional[bool] = None, *, capture_tracing_headers: Optional[bool] = None) -> None -``` - -Add PostHog context and automatic exception capture to an ASGI app. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`app` | `_ASGIApp` | The downstream ASGI application. | *required* -`client` | `Optional[Client]` | Optional destination for automatically captured exceptions. The global client is used by default. Request context still enriches events captured through any client and does not redirect capture calls. | `None` -`capture_exceptions` | `Optional[bool]` | Capture exceptions escaping the downstream app. If omitted, inherit the effective client's `enable_exception_autocapture` setting. Pass `True` or `False` to override it. | `None` -`request_filter` | `Optional[_RequestFilter]` | Optional sync or async callback receiving the ASGI scope. Returning `False` bypasses all instrumentation for that scope. | `None` -`extra_properties` | `Optional[_ExtraProperties]` | Optional sync or async callback receiving the ASGI scope and returning additional event properties. | `None` -`capture_tracing_headers` | `Optional[bool]` | Read PostHog distinct and session ID headers as request-scoped analytics context. Defaults to True. Explicit capture IDs and event properties take precedence. Never use these values for security. | `None` -`trust_tracing_headers` | `Optional[bool]` | Deprecated alias for `capture_tracing_headers`. The capture-named option takes precedence when both are supplied. | `None` - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`app` | | -`capture_exceptions` | | -`capture_tracing_headers` | | -`client` | | -`extra_properties` | | -`request_filter` | | -`trust_tracing_headers` | `bool` | Deprecated alias for `capture_tracing_headers`. - -#### `posthog.integrations.asgi.PosthogASGIMiddleware.app` - -```python -app = app -``` - -#### `posthog.integrations.asgi.PosthogASGIMiddleware.capture_exceptions` - -```python -capture_exceptions = capture_exceptions -``` - -#### `posthog.integrations.asgi.PosthogASGIMiddleware.capture_tracing_headers` - -```python -capture_tracing_headers = _resolve_capture_tracing_headers(capture_tracing_headers, trust_tracing_headers) -``` - -#### `posthog.integrations.asgi.PosthogASGIMiddleware.client` - -```python -client = client -``` - -#### `posthog.integrations.asgi.PosthogASGIMiddleware.extra_properties` - -```python -extra_properties = extra_properties -``` - -#### `posthog.integrations.asgi.PosthogASGIMiddleware.request_filter` - -```python -request_filter = request_filter -``` - -#### `posthog.integrations.asgi.PosthogASGIMiddleware.trust_tracing_headers` - -```python -trust_tracing_headers: bool -``` - -Deprecated alias for `capture_tracing_headers`. - -### `posthog.integrations.celery.PosthogCeleryIntegration` - -```python -PosthogCeleryIntegration(client: Optional[Client] = None, capture_exceptions: bool = True, capture_task_lifecycle_events: bool = True, propagate_context: bool = True, task_filter: Optional[Callable[[Optional[str], dict[str, Any]], bool]] = None) -``` - -Celery integration that captures task lifecycle events and exceptions. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`client` | `Optional[Client]` | Optional `Client` instance. When provided, all events and exceptions are captured through this client rather than the global `posthog` module. Don't skip this if using a custom flag definition cache provider, and pass the custom `Client` instance here initialized with the custom provider so fork safety for that provider is handled correctly. | `None` -`capture_exceptions` | `bool` | Whether to capture task exceptions via `capture_exception` (default `True`). | `True` -`capture_task_lifecycle_events` | `bool` | Whether to emit lifecycle events of the task such as "started", "success", "failure" etc. (default `True`). | `True` -`propagate_context` | `bool` | Whether to propagate PostHog context (distinct ID, session ID, tags) from the producer to the worker via task headers (default `True`). | `True` -`task_filter` | `Optional[Callable[[Optional[str], dict[str, Any]], bool]]` | Optional callback `(task_name, task_properties) -> bool` expected to return `False` if a given task should not be tracked. | `None` - -**Functions:** - -Name | Description ----- | ----------- -`instrument` | Connect Celery signal handlers to capture task events and exceptions. -`shutdown` | Disconnect all signal handlers registered by `instrument()`, flush all pending events -`uninstrument` | Disconnect Celery signal handlers and unregister exit cleanup. - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`capture_exceptions` | | -`capture_task_lifecycle_events` | | -`client` | | -`propagate_context` | | -`task_filter` | | - -#### `posthog.integrations.celery.PosthogCeleryIntegration.capture_exceptions` - -```python -capture_exceptions = capture_exceptions -``` - -#### `posthog.integrations.celery.PosthogCeleryIntegration.capture_task_lifecycle_events` - -```python -capture_task_lifecycle_events = capture_task_lifecycle_events -``` - -#### `posthog.integrations.celery.PosthogCeleryIntegration.client` - -```python -client = client -``` - -#### `posthog.integrations.celery.PosthogCeleryIntegration.instrument` - -```python -instrument() -> None -``` - -Connect Celery signal handlers to capture task events and exceptions. -Call this after initializing the PostHog client and this integration. - -If Celery runs on a single host, reinstrumenting in worker children is -not strictly necessary because the PostHog client and this integration -are fork-safe. If Celery workers run across multiple hosts, each worker -process must initialize PostHog, this integration, and call -`instrument()`. Celery provides `worker_process_init` signal to help -with this. - -#### `posthog.integrations.celery.PosthogCeleryIntegration.propagate_context` - -```python -propagate_context = propagate_context -``` - -#### `posthog.integrations.celery.PosthogCeleryIntegration.shutdown` - -```python -shutdown() -> None -``` - -Disconnect all signal handlers registered by `instrument()`, flush all pending events -and cleanly shutdown the integration. - -`shutdown()` is also registered on `worker_process_shutdown` and `atexit` signals, -but there is no guarantee those will always be called, so we strongly recommend calling -it manually when the integration is no longer needed to avoid data loss. - -#### `posthog.integrations.celery.PosthogCeleryIntegration.task_filter` - -```python -task_filter = task_filter -``` - -#### `posthog.integrations.celery.PosthogCeleryIntegration.uninstrument` - -```python -uninstrument() -> None -``` - -Disconnect Celery signal handlers and unregister exit cleanup. - -Do not use directly, call `shutdown()` instead. - -### `posthog.request.set_socket_options` - -```python -set_socket_options(socket_options: Optional[SocketOptions]) -> None -``` - -Configure socket options for all SDK HTTP connections. - -Call this during initialization, before making API requests. Pass `None` -to reset to the default socket behavior. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`socket_options` | `Optional[SocketOptions]` | A list of `(level, option, value)` tuples accepted by urllib3/`socket.setsockopt()`, or `None` to reset defaults. | *required* - -
-Example - -from posthog import set_socket_options -set_socket_options([(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)]) - -
- -### `posthog.request.enable_keep_alive` - -```python -enable_keep_alive() -> None -``` - -Enable TCP keepalive for SDK HTTP connections. - -This helps prevent idle pooled connections from being dropped by network -infrastructure. Call during initialization, before making API requests. - -### `posthog.request.disable_connection_reuse` - -```python -disable_connection_reuse() -> None -``` - -Disable HTTP connection reuse for SDK requests. - -Each request will create a fresh connection. This can avoid issues with -environments that terminate pooled connections, but adds per-request -overhead. Call during initialization, before making API requests. - -## AsyncClient - -Needs the `async` extra, `posthog[async]`. - -### `AsyncClient.alias` - -```python -alias(previous_id: ID_TYPES, distinct_id: Optional[str], timestamp: Optional[Union[datetime, str]] = None, uuid: Optional[str] = None, disable_geoip: Optional[bool] = None) -> Optional[str] -``` - -### `AsyncClient.capture` - -```python -capture(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] -``` - -Queue an event without blocking for network delivery. - -### `AsyncClient.capture_immediate` - -```python -capture_immediate(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] -``` - -Capture one event and wait until its delivery attempt completes. - -### `AsyncClient.capture_exception` - -```python -capture_exception(exception: Optional[ExceptionArg] = None, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] -``` - -Capture an exception. This method never raises, including in debug mode. - -### `AsyncClient.emails` - -```python -emails: AsyncEmails -``` - -Send transactional email from PostHog email templates, and read the -status of each email sent with `get`. - -Needs `secret_key`, a project secret API key with the `hog_flow:write` -scope, and `posthog[async]`. Sends go to `app_host`, or to the PostHog -Cloud app host of `host`. `email_request_timeout_seconds` and -`email_request_max_retries` configure each send. A disabled or shut-down -client sends nothing. See `posthog.emails` for setup. - -**Examples:** - -```python -await client.emails.send( - to="user@example.com", - template_id="0190f6d2-7c1e-7b3a-9f5e-1c2d3e4f5a6b", - distinct_id="user_123", -) -``` - -### `AsyncClient.flush` - -```python -flush(timeout_seconds: Optional[float] = 10) -> None -``` - -### `AsyncClient.evaluate_flags` - -```python -evaluate_flags(distinct_id: Optional[ID_TYPES] = None, *, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, disable_geoip: Optional[bool] = None, flag_keys: Optional[list[str]] = None, device_id: Optional[str] = None) -> FeatureFlagEvaluations -``` - -Evaluate flags remotely without blocking the event loop. - -The returned snapshot has synchronous in-memory accessors. Pass it to -`capture(flags=...)` to attach the exact values used for branching. -This client does not evaluate flags locally, so the snapshot's -`unresolved_flags` is always empty. - -### `AsyncClient.group_identify` - -```python -group_identify(group_type: str, group_key: str, properties: Optional[Dict[str, Any]] = None, timestamp: Optional[Union[datetime, str]] = None, uuid: Optional[Union[str, UUID]] = None, disable_geoip: Optional[bool] = None, distinct_id: Optional[ID_TYPES] = None) -> Optional[str] -``` - -### `AsyncClient.set` - -```python -set(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str] -``` - -### `AsyncClient.set_once` - -```python -set_once(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str] -``` - -### `AsyncClient(...)` - -```python -__init__(project_api_key: str, host: Optional[str] = None, debug: bool = False, max_queue_size: int = 10000, send: bool = True, on_error: bool = None, flush_at: int = 100, flush_interval: float = 5.0, gzip: bool = False, max_retries: int = 3, timeout: float = 15, thread: int = 1, disabled: bool = False, disable_geoip: bool = True, is_server: bool = True, historical_migration: bool = False, super_properties: Optional[dict[str, Any]] = None, before_send: Optional[dict[str, Any]] = None, log_captured_exceptions: bool = False, project_root: Optional[str] = None, capture_exception_code_variables: bool = False, code_variables_mask_patterns: bool = None, code_variables_ignore_patterns: bool = None, code_variables_mask_url_credentials: bool = None, code_variables_detect_secrets: bool = None, in_app_modules: Optional[list[str]] = None, capture_mode: Optional[Union[CaptureMode, str]] = None, capture_compression: Optional[Union[CaptureCompression, str]] = None, capture_trace_context: bool = False, secret_key: Optional[str] = None, personal_api_key: Optional[str] = None, feature_flags_request_timeout_seconds: float = 3, feature_flags_request_max_retries: int = 1, app_host: Optional[str] = None, email_request_timeout_seconds: float = 15, email_request_max_retries: int = 2) -> None -``` - -### `AsyncClient.shutdown` - -```python -shutdown() -> None -``` - -### `AsyncClient.join` - -```python -join() -> None -``` - -### `AsyncClient.get_remote_config_payload` - -```python -get_remote_config_payload(key: str) -> Optional[Any] -``` - -Fetch and decrypt a remote-config payload without blocking the loop. - -## Module functions - -### `posthog.new_context` - -```python -new_context(fresh: bool = False, capture_exceptions: Optional[bool] = None, client: Optional[Client] = None) -``` - -Create a new context scope that will be active for the duration of the with block. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`fresh` | `bool` | Whether to start with a fresh context (default: False) | `False` -`capture_exceptions` | `Optional[bool]` | Whether to capture exceptions raised within the context. If omitted, defaults to the relevant client's exception autocapture setting. | `None` -`client` | `Optional[Client]` | Optional Posthog client instance to use for this context (default: None) | `None` - -**Examples:** - -```python -from posthog import new_context, tag, capture -with new_context(): - tag("request_id", "123") - capture("event_name", properties={"property": "value"}) -``` - -
-Category - -Contexts - -
- -### `posthog.scoped` - -```python -scoped(fresh = False, capture_exceptions: Optional[bool] = None) -``` - -Decorator that creates a new context for the function. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`fresh` | | Whether to start with a fresh context (default: False) | `False` -`capture_exceptions` | `Optional[bool]` | Whether to capture and track exceptions with posthog error tracking. If omitted, defaults to the global exception autocapture setting. | `None` - -**Examples:** - -```python -from posthog import scoped, tag, capture -@scoped() -def process_payment(payment_id): - tag("payment_id", payment_id) - capture("payment_started") -``` - -
-Category - -Contexts - -
- -### `posthog.set_context_session` - -```python -set_context_session(session_id: str) -``` - -Set the session ID for the current context. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`session_id` | `str` | The session ID to associate with the current context and its children | *required* - -**Examples:** - -```python -from posthog import set_context_session -set_context_session("session_123") -``` - -
-Category - -Contexts - -
- -### `posthog.set_context_device_id` - -```python -set_context_device_id(device_id: str) -``` - -Set the device ID for the current context, associating all feature flag requests -in this or child contexts with the given device ID. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`device_id` | `str` | The device ID to associate with the current context and its children | *required* - -**Examples:** - -```python -from posthog import set_context_device_id -set_context_device_id("device_123") -``` - -
-Category - -Contexts - -
- -### `posthog.identify_context` - -```python -identify_context(distinct_id: str) -``` - -Identify the current context with a distinct ID. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | `str` | The distinct ID to associate with the current context and its children | *required* - -**Examples:** - -```python -from posthog import identify_context -identify_context("user_123") -``` - -
-Category - -Identification - -
- -### `posthog.set_capture_exception_code_variables_context` - -```python -set_capture_exception_code_variables_context(enabled: bool) -``` - -Override code-variable capture for exceptions in the current context. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`enabled` | `bool` | Whether exceptions captured in this context should include local variable values from stack frames. | *required* - -
-Category - -Contexts - -
- -### `posthog.set_code_variables_mask_patterns_context` - -```python -set_code_variables_mask_patterns_context(mask_patterns: list[str]) -``` - -Override code-variable mask patterns for exceptions in the current context. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`mask_patterns` | `list[str]` | Variable-name patterns whose values should be replaced with `***` when code variables are captured. | *required* - -
-Category - -Contexts - -
- -### `posthog.set_code_variables_ignore_patterns_context` - -```python -set_code_variables_ignore_patterns_context(ignore_patterns: list[str]) -``` - -Override code-variable ignore patterns for exceptions in the current context. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`ignore_patterns` | `list[str]` | Variable-name patterns that should be omitted entirely when code variables are captured. | *required* - -
-Category - -Contexts - -
- -### `posthog.set_code_variables_mask_url_credentials_context` - -```python -set_code_variables_mask_url_credentials_context(enabled: bool) -``` - -Whether to scrub credentials embedded in URLs/DSNs (e.g. user:pass@host) from -captured code variables for the current context. - -### `posthog.set_code_variables_detect_secrets_context` - -```python -set_code_variables_detect_secrets_context(enabled: bool) -``` - -Whether to apply entropy-based secret detection as a last-resort redaction of -high-entropy values (API keys, tokens, strong passwords) in captured code -variables for the current context. - -
-Category - -Contexts - -
- -### `posthog.tag` - -```python -tag(name: str, value: Any) -``` - -Add a tag to the current context. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`name` | `str` | The tag key | *required* -`value` | `Any` | The tag value | *required* - -**Examples:** - -```python -from posthog import tag -tag("user_id", "123") -``` - -
-Category - -Contexts - -
- -### `posthog.get_tags` - -```python -get_tags() -> Dict[str, Any] -``` - -Get all tags from the current context. - -**Returns:** - -Type | Description ----- | ----------- -`Dict[str, Any]` | Dict of all tags in the current context - -
-Category - -Contexts - -
- -### `posthog.capture` - -```python -capture(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] -``` - -Capture anything a user does within your system. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`event` | `str` | The event name to specify the event | *required* -`**kwargs` | `Unpack[OptionalCaptureArgs]` | Optional arguments including: distinct_id: Unique identifier for the user properties: Dict of event properties timestamp: When the event occurred. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. uuid: Unique identifier for this event. If omitted, one is generated and returned. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. groups: Dict of group types and IDs flags: A FeatureFlagEvaluations snapshot from evaluate_flags(). The exact values from the snapshot are attached with no extra /flags request. send_feature_flags: Deprecated. Prefer flags=... from evaluate_flags(). When truthy, evaluates flags during capture and attaches them to the event. disable_geoip: Whether to disable GeoIP lookup | `{}` - -
-Details - -Capture allows you to capture anything a user does within your system, which you can later use in PostHog to find patterns in usage, work out which features to improve or where people are giving up. A capture call requires an event name to specify the event. We recommend using [verb] [noun], like `movie played` or `movie updated` to easily identify what your events mean later on. Capture takes a number of optional arguments, which are defined by the `OptionalCaptureArgs` type. - -
- -**Examples:** - -```python -# Context and capture usage -from posthog import new_context, identify_context, tag_context, capture -# Enter a new context (e.g. a request/response cycle, an instance of a background job, etc) -with new_context(): - # Associate this context with some user, by distinct_id - identify_context('some user') - - # Capture an event, associated with the context-level distinct ID ('some user') - capture('movie started') - - # Capture an event associated with some other user (overriding the context-level distinct ID) - capture('movie joined', distinct_id='some-other-user') - - # Capture an event with some properties - capture('movie played', properties={'movie_id': '123', 'category': 'romcom'}) - - # Capture an event with some properties - capture('purchase', properties={'product_id': '123', 'category': 'romcom'}) - # Capture an event with some associated group - capture('purchase', groups={'company': 'id:5'}) - - # Adding a tag to the current context will cause it to appear on all subsequent events - tag_context('some-tag', 'some-value') - - capture('another-event') # Will be captured with `'some-tag': 'some-value'` in the properties dict -``` -```python -# Set event properties -from posthog import capture -capture( - "user_signed_up", - distinct_id="distinct_id_of_the_user", - properties={ - "login_type": "email", - "is_free_trial": "true" - } -) -``` - -### `posthog.capture_ai` - -```python -capture_ai(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] -``` - -Capture an AI event on the dedicated AI capture endpoint. - -Beta: the signature is stable; operational limits (per-event size cap, -batching, endpoint) may change without notice. - -Takes the same arguments and returns the same value as `capture()`: the -event UUID, or None when the event was not admitted (disabled client, or -dropped by `before_send`). The event is delivered on an isolated queue -with its own consumer pool and a higher per-event size cap, posting to -the dedicated AI ingestion endpoint. The payload is sent as given — no -redaction or truncation is applied here. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`event` | `str` | The event name, normally one of the `$ai_*` event names. | *required* -`**kwargs` | `Unpack[OptionalCaptureArgs]` | Same optional arguments as `capture()`. | `{}` - -**Examples:** - -```python -from posthog import capture_ai - -uuid = capture_ai( - "$ai_generation", - distinct_id="user_123", - properties={"$ai_model": "gpt-5"}, -) -``` - -### `posthog.set` - -```python -set(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str] -``` - -Set properties on a user record. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`**kwargs` | `Unpack[OptionalSetArgs]` | Optional arguments including: distinct_id: Unique identifier for the user. Falls back to the context distinct ID; if none exists, this call does nothing. properties: Dict of person properties to set. timestamp: When the properties were set. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. uuid: Unique identifier for this operation. If omitted, one is generated and returned. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. disable_geoip: Whether to disable GeoIP lookup. | `{}` - -
-Details - -This will overwrite previous people property values. Generally operates similar to `capture`, with distinct_id being an optional argument, defaulting to the current context's distinct ID. If there is no context-level distinct ID, and no override distinct_id is passed, this function will do nothing. Context tags are folded into $set properties, so tagging the current context and then calling `set` will cause those tags to be set on the user (unlike capture, which causes them to just be set on the event). - -
- -**Examples:** - -```python -# Set person properties -from posthog import set -set(distinct_id='distinct_id', properties={'name': 'Max Hedgehog'}) -``` - -### `posthog.set_once` - -```python -set_once(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str] -``` - -Set properties on a user record, only if they do not yet exist. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`**kwargs` | `Unpack[OptionalSetArgs]` | Optional arguments including: distinct_id: Unique identifier for the user. Falls back to the context distinct ID; if none exists, this call does nothing. properties: Dict of person properties to set only once. timestamp: When the properties were set. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. uuid: Unique identifier for this operation. If omitted, one is generated and returned. If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. disable_geoip: Whether to disable GeoIP lookup. | `{}` - -
-Details - -This will not overwrite previous people property values, unlike `set`. Otherwise, operates in an identical manner to `set`. - -
- -**Examples:** - -```python -# Set property once -from posthog import set_once -set_once(distinct_id='distinct_id', properties={'initial_url': '/blog'}) - -``` - -### `posthog.group_identify` - -```python -group_identify(group_type: str, group_key: str, properties: Optional[Dict[str, Any]] = None, timestamp: Optional[Union[datetime.datetime, str]] = None, uuid: Optional[str] = None, disable_geoip: Optional[bool] = None, distinct_id: Optional[ID_TYPES] = None) -> Optional[str] -``` - -Set properties on a group. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`group_type` | `str` | Type of your group. Required - the call is dropped with a warning if it is missing or empty. | *required* -`group_key` | `str` | Unique identifier of the group. Required - the call is dropped with a warning if it is missing or empty. | *required* -`properties` | `Optional[Dict[str, Any]]` | Properties to set on the group | `None` -`timestamp` | `Optional[Union[datetime, str]]` | Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | `None` -`uuid` | `Optional[str]` | Optional UUID for the event | `None` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup | `None` -`distinct_id` | `Optional[ID_TYPES]` | Optional distinct ID of the user performing the action | `None` - -**Examples:** - -```python -# Group identify -from posthog import group_identify -group_identify('company', 'company_id_in_your_db', { - 'name': 'Awesome Inc.', - 'employees': 11 -}) -``` - -### `posthog.alias` - -```python -alias(previous_id: ID_TYPES, distinct_id: str, timestamp: Optional[Union[datetime.datetime, str]] = None, uuid: Optional[str] = None, disable_geoip: Optional[bool] = None) -> Optional[str] -``` - -Associate user behaviour before and after they e.g. register, login, or perform some other identifying action. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`previous_id` | `ID_TYPES` | The unique ID of the user before | *required* -`distinct_id` | `str` | The current unique id | *required* -`timestamp` | `Optional[Union[datetime, str]]` | Optional timestamp for the event. UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | `None` -`uuid` | `Optional[str]` | Optional UUID for the event | `None` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup | `None` - -
-Details - -To marry up whatever a user does before they sign up or log in with what they do after you need to make an alias call. This will allow you to answer questions like "Which marketing channels leads to users churning after a month?" or "What do users do on our website before signing up?". Particularly useful for associating user behaviour before and after they e.g. register, login, or perform some other identifying action. - -
- -**Examples:** - -```python -# Alias user -from posthog import alias -alias(previous_id='distinct_id', distinct_id='alias_id') -``` - -### `posthog.capture_exception` - -```python -capture_exception(exception: Optional[ExceptionArg] = None, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str] -``` - -Capture exceptions that happen in your code. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`exception` | `Optional[ExceptionArg]` | The exception to capture. If not provided, the current exception is captured via `sys.exc_info()` | `None` -`**kwargs` | `Unpack[OptionalCaptureArgs]` | Optional capture arguments including distinct_id, properties, timestamp, uuid, groups, flags, send_feature_flags, and disable_geoip. Overriding reserved exception properties through `properties` is deprecated and will stop working in the next major version. | `{}` - -
-Details - -Capture exception is idempotent - if it is called twice with the same exception instance, only a occurrence will be tracked in posthog. This is because, generally, contexts will cause exceptions to be captured automatically. However, to ensure you track an exception, if you catch and do not re-raise it, capturing it manually is recommended, unless you are certain it will have crossed a context boundary (e.g. by existing a `with posthog.new_context():` block already). If the passed exception was raised and caught, the captured stack trace will consist of every frame between where the exception was raised and the point at which it is captured (the "traceback"). If the passed exception was never raised, e.g. if you call `posthog.capture_exception(ValueError("Some Error"))`, the stack trace captured will be the full stack trace at the moment the exception was captured. Note that heavy use of contexts will lead to truncated stack traces, as the exception will be captured by the context entered most recently, which may not be the point you catch the exception for the final time in your code. It's recommended to use contexts sparingly, for this reason. `capture_exception` takes the same set of optional arguments as `capture`. - -
- -**Examples:** - -```python -# Capture exception -from posthog import capture_exception -try: - risky_operation() -except Exception as e: - capture_exception(e) -``` - -### `posthog.feature_enabled` - -```python -feature_enabled(key: str, distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[bool] -``` - -Use feature flags to enable or disable features for users. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The feature flag key | *required* -`distinct_id` | `ID_TYPES` | The user's distinct ID | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | Groups mapping | `None` -`person_properties` | `Optional[Dict[str, Any]]` | Person properties | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties | `None` -`only_evaluate_locally` | `bool` | Whether to evaluate only locally | `False` -`send_feature_flag_events` | `bool` | Whether to send feature flag events | `True` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup | `None` -`device_id` | `Optional[str]` | Optional device ID override for experience-continuity flags | `None` - -
-Details - -You can call `posthog.load_feature_flags()` before to make sure you're not doing unexpected requests. - -
- -**Examples:** - -```python -# Boolean feature flag -from posthog import feature_enabled, get_feature_flag_payload -is_my_flag_enabled = feature_enabled('flag-key', 'distinct_id_of_your_user') -if is_my_flag_enabled: - matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user') -``` - -### `posthog.get_feature_flag` - -```python -get_feature_flag(key: str, distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[FlagValue] -``` - -Get feature flag variant for users. Used with experiments. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The feature flag key | *required* -`distinct_id` | `ID_TYPES` | The user's distinct ID | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | Groups mapping from group type to group key | `None` -`person_properties` | `Optional[Dict[str, Any]]` | Person properties | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties in format { group_type_name: { group_properties } } | `None` -`only_evaluate_locally` | `bool` | Whether to evaluate only locally | `False` -`send_feature_flag_events` | `bool` | Whether to send feature flag events | `True` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup | `None` -`device_id` | `Optional[str]` | Optional device ID override for experience-continuity flags | `None` - -
-Details - -`groups` are a mapping from group type to group key. So, if you have a group type of "organization" and a group key of "5", you would pass groups={"organization": "5"}. `group_properties` take the format: { group_type_name: { group_properties } }. So, for example, if you have the group type "organization" and the group key "5", with the properties name, and employee count, you'll send these as: group_properties={"organization": {"name": "PostHog", "employees": 11}}. - -
- -**Examples:** - -```python -# Multivariate feature flag -from posthog import get_feature_flag, get_feature_flag_payload -enabled_variant = get_feature_flag('flag-key', 'distinct_id_of_your_user') -if enabled_variant == 'variant-key': - matched_flag_payload = get_feature_flag_payload('flag-key', 'distinct_id_of_your_user') -``` - -### `posthog.get_all_flags` - -```python -get_all_flags(distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None, flag_keys_to_evaluate: Optional[list[str]] = None) -> Optional[dict[str, FlagValue]] -``` - -Get all flags for a given user. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | `ID_TYPES` | The user's distinct ID | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | Groups mapping | `None` -`person_properties` | `Optional[Dict[str, Any]]` | Person properties | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties | `None` -`only_evaluate_locally` | `bool` | Whether to evaluate only locally | `False` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup | `None` -`device_id` | `Optional[str]` | Optional device ID override for experience-continuity flags | `None` -`flag_keys_to_evaluate` | `Optional[list[str]]` | Optional list of flag keys to evaluate (evaluates all if None) | `None` - -
-Details - -Flags are key-value pairs where the key is the flag key and the value is the flag variant, or True, or False. - -
- -**Examples:** - -```python -# All flags for user -from posthog import get_all_flags -get_all_flags('distinct_id_of_your_user') -``` - -### `posthog.get_feature_flag_result` - -```python -get_feature_flag_result(key: str, distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[FeatureFlagResult] -``` - -Get a FeatureFlagResult object which contains the flag result and payload. - -This method evaluates a feature flag and returns a FeatureFlagResult object containing: -- enabled: Whether the flag is enabled -- variant: The variant value if the flag has variants -- payload: The payload associated with the flag (automatically deserialized from JSON) -- key: The flag key -- reason: Why the flag was enabled/disabled - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The feature flag key. | *required* -`distinct_id` | `ID_TYPES` | The user's distinct ID. | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | Mapping of group type to group key. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | Person properties to use for evaluation. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties keyed by group type. | `None` -`only_evaluate_locally` | `bool` | Whether to evaluate only locally. | `False` -`send_feature_flag_events` | `bool` | Whether to send a $feature_flag_called event. | `True` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup. | `None` -`device_id` | `Optional[str]` | Optional device ID override for experience-continuity flags. | `None` - -Example: -```python -result = posthog.get_feature_flag_result('beta-feature', 'distinct_id') -if result and result.enabled: - # Use the variant and payload - print(f"Variant: {result.variant}") - print(f"Payload: {result.payload}") -``` - -### `posthog.get_feature_flag_payload` - -```python -get_feature_flag_payload(key: str, distinct_id: ID_TYPES, match_value: Optional[FlagValue] = None, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, send_feature_flag_events: bool = True, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None) -> Optional[object] -``` - -Get the payload associated with a feature flag value. - -Deprecated for new code. Prefer `evaluate_flags()` and -`flags.get_flag_payload(key)` so flag evaluation happens once per request. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The feature flag key. | *required* -`distinct_id` | `ID_TYPES` | The user's distinct ID. | *required* -`match_value` | `Optional[FlagValue]` | Optional flag value to use when selecting a payload. | `None` -`groups` | `Optional[Mapping[str, Union[str, int]]]` | Mapping of group type to group key. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | Person properties to use for evaluation. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties keyed by group type. | `None` -`only_evaluate_locally` | `bool` | Whether to evaluate only locally. | `False` -`send_feature_flag_events` | `bool` | Whether to send a $feature_flag_called event. | `True` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup. | `None` -`device_id` | `Optional[str]` | Optional device ID override for experience-continuity flags. | `None` - -**Returns:** - -Type | Description ----- | ----------- -`Optional[object]` | The payload associated with the matched feature flag value, or None. -`Optional[object]` | This function returns the payload only, not the FeatureFlagResult wrapper -`Optional[object]` | used internally to compute it. - -### `posthog.get_remote_config_payload` - -```python -get_remote_config_payload(key: str) -``` - -Get the payload for a remote config feature flag. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The key of the feature flag | *required* - -**Returns:** - -Type | Description ----- | ----------- - | The payload associated with the feature flag. If payload is encrypted, the return value will be decrypted - -
-Note - -Requires secret_key to be set for authentication - -
- -### `posthog.get_all_flags_and_payloads` - -```python -get_all_flags_and_payloads(distinct_id: ID_TYPES, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, device_id: Optional[str] = None, flag_keys_to_evaluate: Optional[list[str]] = None) -> FlagsAndPayloads -``` - -Get all feature flag values and payloads for a user. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | `ID_TYPES` | The user's distinct ID. | *required* -`groups` | `Optional[Mapping[str, Union[str, int]]]` | Mapping of group type to group key. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | Person properties to use for evaluation. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties keyed by group type. | `None` -`only_evaluate_locally` | `bool` | Whether to evaluate only locally. | `False` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup. | `None` -`device_id` | `Optional[str]` | Optional device ID override for experience-continuity flags. | `None` -`flag_keys_to_evaluate` | `Optional[list[str]]` | Optional list of flag keys to evaluate. Evaluates all flags when omitted. | `None` - -**Returns:** - -Type | Description ----- | ----------- -`FlagsAndPayloads` | A dict with `featureFlags` and `featureFlagPayloads` entries. - -### `posthog.evaluate_flags` - -```python -evaluate_flags(distinct_id: Optional[ID_TYPES] = None, groups: Optional[Mapping[str, Union[str, int]]] = None, person_properties: Optional[Dict[str, Any]] = None, group_properties: Optional[Dict[str, Dict[str, Any]]] = None, only_evaluate_locally: bool = False, disable_geoip: Optional[bool] = None, flag_keys: Optional[list[str]] = None, device_id: Optional[str] = None) -> FeatureFlagEvaluations -``` - -Evaluate all feature flags for a user in a single call and return a -`FeatureFlagEvaluations` snapshot. Branch on `.is_enabled()` / -`.get_flag()` and pass the same snapshot to `capture()` via the -`flags` option so events carry the exact flag values the code branched on. - -Prefer this over repeated `get_feature_flag()` calls and over -`capture(send_feature_flags=True)` — it consolidates flag evaluation into -a single `/flags` request per incoming request. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | `Optional[ID_TYPES]` | The user's distinct ID. If `None`, falls back to the context distinct_id. If still unresolvable, returns an empty snapshot. | `None` -`groups` | `Optional[Mapping[str, Union[str, int]]]` | Mapping of group type to group key. | `None` -`person_properties` | `Optional[Dict[str, Any]]` | Person properties to use for evaluation. | `None` -`group_properties` | `Optional[Dict[str, Dict[str, Any]]]` | Group properties keyed by group type. | `None` -`only_evaluate_locally` | `bool` | If `True`, never fall back to remote evaluation and omit flags that cannot be evaluated locally. The snapshot's `unresolved_flags` lists them with a reason. | `False` -`disable_geoip` | `Optional[bool]` | Whether to disable GeoIP lookup. | `None` -`flag_keys` | `Optional[list[str]]` | Optional list that scopes local evaluation, the underlying `/flags` request, and the returned snapshot. When omitted or `None`, all flags are evaluated. An empty list returns an empty snapshot without evaluating flags. A requested key absent from loaded local definitions is included in one remote fallback per `evaluate_flags` call unless `only_evaluate_locally` is `True`. If the server also does not know the key, it is omitted from the snapshot. | `None` -`device_id` | `Optional[str]` | Optional device ID override. If not provided, falls back to the context device_id (which may be set via tracing headers). Used by experience-continuity flags to match users across distinct_id changes. | `None` - -**Examples:** - -```python -from posthog import evaluate_flags, capture -flags = evaluate_flags("user_123", person_properties={"plan": "enterprise"}) -if flags.is_enabled("new-dashboard"): - render_new_dashboard() -capture("page_viewed", distinct_id="user_123", flags=flags) -``` - -### `posthog.feature_flag_definitions` - -```python -feature_flag_definitions() -``` - -Returns loaded feature flags. - -
-Details - -Returns loaded feature flags, if any. Helpful for debugging what flag information you have loaded. - -
- -**Examples:** - -```python -from posthog import feature_flag_definitions -definitions = feature_flag_definitions() -``` - -### `posthog.get_feature_flag_evaluation_runtime` - -```python -get_feature_flag_evaluation_runtime(key: str) -> Optional[FeatureFlagEvaluationRuntime] -``` - -Return where a locally loaded feature flag is meant to be evaluated. - -
-Details - -Reads the `evaluation_runtime` each flag definition carries, so no extra -request is made. Returns `None` when local evaluation has not loaded a -definition for this key. A definition that carries no runtime reports -`FeatureFlagEvaluationRuntime.ALL`, the default PostHog applies. - -
- -**Examples:** - -```python -from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_evaluation_runtime -runtime = get_feature_flag_evaluation_runtime("my-flag") -``` - -
-Category - -Feature flags - -
- -### `posthog.get_feature_flag_keys_by_evaluation_runtime` - -```python -get_feature_flag_keys_by_evaluation_runtime(evaluation_runtime: Union[FeatureFlagEvaluationRuntime, str]) -> list[str] -``` - -Return the keys of locally loaded flags that a runtime can evaluate. - -
-Details - -A flag set to `FeatureFlagEvaluationRuntime.ALL` suits either runtime, so -it is returned for `CLIENT` and for `SERVER`. Use this to decide which -flags to hand to a browser when a backend serves flags to its own -frontend. - -
- -**Examples:** - -```python -from posthog import FeatureFlagEvaluationRuntime, get_feature_flag_keys_by_evaluation_runtime -client_keys = get_feature_flag_keys_by_evaluation_runtime(FeatureFlagEvaluationRuntime.CLIENT) -``` - -
-Category - -Feature flags - -
- -### `posthog.load_feature_flags` - -```python -load_feature_flags() -``` - -Load feature flag definitions from PostHog. - -**Examples:** - -```python -from posthog import load_feature_flags -load_feature_flags() -``` - -### `posthog.flush` - -```python -flush(timeout_seconds: Optional[float] = 10) -> None -``` - -Attempt immediate delivery of queued events and ended spans, keeping the global client reusable. - -Call `flush()` before a short-lived request or serverless invocation returns -when using the shared module-level client. Use `shutdown()` only for final -process cleanup or a client that will not be reused. Flush does not stop the -background workers or feature flag poller, and does not end open spans. - -Returning does not guarantee server receipt: pending work can remain after a -timeout or span export failure, and failed events may be dropped by consumers. -Calls made directly from SDK callbacks such as `on_error` are deferred -rather than blocking the worker that invoked the callback. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`timeout_seconds` | `Optional[float]` | Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass `None` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently, waiting up to this many seconds for an in-flight flush before starting a fresh drain budget. A lock wait timeout leaves the in-flight flush to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. The last span request is not cut short and is bounded by the client's `timeout` in seconds, so this is not a strict wall-clock limit for the whole call. With `None`, a retriable span failure is left for a later flush rather than retried within this call. | `10` - -**Examples:** - -```python -import posthog -posthog.capture('event_name', distinct_id='user_id') -# Before a serverless invocation returns; keep the global client open. -posthog.flush(timeout_seconds=10) -``` - -### `posthog.join` - -```python -join() -> None -``` - -Attempt to process queued events and stop the client's background workers. Use `shutdown()` directly in most cases. - -Failed or undrainable events may be dropped and reported through logging or -`on_error`; returning does not guarantee server receipt. Lifecycle cleanup -is attempted once, and cleanup failures are logged without retry. - -**Examples:** - -```python -from posthog import join -join() -``` - -### `posthog.start_span` - -```python -start_span(name: str, *, kind: Optional[str] = None, attributes: Optional[Mapping[str, Any]] = None, parent: Union[Span, str, None] = None, tracestate: Optional[str] = None, start_time: Union[datetime.datetime, float, None] = None) -> Span -``` - -Start a span for distributed tracing. Alpha. - -Returns a span handle. Use it as a context manager to make it the active -span for the block and end it on exit (recording a raised exception on the -way out); or call `end()` yourself for a span that cannot wrap a block. -Spans started inside the block nest under it automatically. Always returns -a usable handle, even when tracing is off, so calling code never branches. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`name` | `str` | A low-cardinality operation name, e.g. `GET /users/:id`. Variable values belong in attributes, not the name. | *required* -`kind` | `Optional[str]` | `internal` (default), `server`, `client`, `producer` or `consumer`. | `None` -`attributes` | `Optional[Mapping[str, Any]]` | Initial attributes. | `None` -`parent` | `Union[Span, str, None]` | A span handle, or an inbound W3C `traceparent` header value to continue a remote trace. Defaults to the active span. | `None` -`tracestate` | `Optional[str]` | The inbound `tracestate` header accompanying a `traceparent` string `parent`; preserved and propagated. | `None` -`start_time` | `Union[datetime, float, None]` | A `datetime` or epoch seconds, to backdate the span. | `None` - -**Examples:** - -```python -import posthog -posthog.traces = {"service_name": "checkout-api"} - -with posthog.start_span("POST /checkout", parent=request.headers.get("traceparent")) as span: - span.set_attribute("plan", user.plan) - with posthog.start_span("db.query", kind="client"): - ... - outgoing_headers = {"traceparent": span.traceparent()} -``` - -### `posthog.get_active_span` - -```python -get_active_span() -> Optional[Span] -``` - -The span that is active in the current context, or `None`. Alpha. - -Only entering a span (`with posthog.start_span(...) as span:`) makes it -active; a span started manually is not. Use it to propagate the trace to -the next service: `span.traceparent()` is the header value. - -**Examples:** - -```python -span = posthog.get_active_span() -if span is not None: - headers["traceparent"] = span.traceparent() -``` - -### `posthog.shutdown` - -```python -shutdown() -> None -``` - -Attempt final delivery and permanently shut down the global client. - -Call this for final process cleanup, not at the end of each request or -serverless invocation that shares the module-level client. Use `flush()` -before returning from those invocations instead. Shutdown closes capture -lanes, stops background workers and the feature flag poller, and tears down -integrations. The global client is not recreated automatically after shutdown. - -This normally blocks until queued events have been attempted and cleanup -finishes. Failed or undrainable events may be dropped and reported through -logging or `on_error`; returning does not guarantee server receipt. Event -draining has no timeout. Queued spans get a final flush with up to 30 seconds -waiting for an in-flight flush, then a fresh 30-second drain budget; the last -HTTP request is not cut short. Unsent queued spans and still-open spans are -discarded with a warning. -Lifecycle cleanup is attempted once, and cleanup failures are logged without -retry. Calls made directly from SDK callbacks such as `on_error` are deferred -to avoid deadlocking the worker. If blocking completion is required, signal an application-owned -thread, return from the callback, and call `shutdown()` from that thread. -Do not wait inside a callback for another thread or task calling a lifecycle -method. - -**Examples:** - -```python -from posthog import shutdown -shutdown() -``` - -### `posthog.setup` - -```python -setup() -> Client -``` - -Create or return the global PostHog client configured by module settings. - -Most applications should either instantiate `Posthog` directly or set -`posthog.api_key`/other module settings before calling top-level helpers. -`setup()` is called automatically by global APIs such as `capture()`. - -**Returns:** - -Type | Description ----- | ----------- -`Client` | The global `Client` instance. If both `api_key` and -`Client` | `project_api_key` are missing or blank, the client is disabled and -`Client` | module-level calls become no-ops. - -## Types - -### `OptionalCaptureArgs` - -Bases: `TypedDict` - -Optional arguments for the capture method. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`distinct_id` | | Unique identifier for the person associated with this event. If not set, the context distinct_id is used, if available, otherwise a UUID is generated, and the event is marked as personless. Setting context-level distinct_id's is recommended. | -`properties` | | Dictionary of properties to track with the event | -`timestamp` | | When the event occurred (defaults to current time). UTC is preferred; non-UTC datetimes and parseable ISO timestamp strings are converted to UTC. | -`uuid` | | Unique identifier for this specific event. If not provided, one is generated. The event UUID is returned, so you can correlate it with actions in your app (like showing users an error ID if you capture an exception). If provided, it must be a valid UUID string or uuid.UUID instance; invalid values are ignored and replaced with a newly generated UUID. | -`groups` | | Group identifiers to associate with this event (format: {group_type: group_key}) | -`flags` | | A `FeatureFlagEvaluations` snapshot from `evaluate_flags()`. The exact flag values from the snapshot are attached to the event with no additional network call — prefer this over `send_feature_flags`. | -`send_feature_flags` | | Deprecated — prefer `flags` with a `FeatureFlagEvaluations` snapshot. Whether to include currently active feature flags in the event properties. Can be a boolean or a SendFeatureFlagsOptions object. Defaults to False. Fires a hidden `/flags` request on capture and may return different values than the ones the code branched on. | -`disable_geoip` | | Whether to disable GeoIP lookup for this event. Defaults to False. | - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`disable_geoip` | `NotRequired[Optional[bool]]` | -`distinct_id` | `NotRequired[Optional[ID_TYPES]]` | -`flags` | `NotRequired[Optional[FeatureFlagEvaluations]]` | -`groups` | `NotRequired[Optional[Dict[str, str]]]` | -`properties` | `NotRequired[Optional[Dict[str, Any]]]` | -`send_feature_flags` | `NotRequired[Optional[Union[bool, SendFeatureFlagsOptions]]]` | -`timestamp` | `NotRequired[Optional[Union[datetime, str]]]` | -`uuid` | `NotRequired[Optional[Union[str, UUID]]]` | - -#### `posthog.args.OptionalCaptureArgs.disable_geoip` - -```python -disable_geoip: NotRequired[Optional[bool]] -``` - -#### `posthog.args.OptionalCaptureArgs.distinct_id` - -```python -distinct_id: NotRequired[Optional[ID_TYPES]] -``` - -#### `posthog.args.OptionalCaptureArgs.flags` - -```python -flags: NotRequired[Optional[FeatureFlagEvaluations]] -``` - -#### `posthog.args.OptionalCaptureArgs.groups` - -```python -groups: NotRequired[Optional[Dict[str, str]]] -``` - -#### `posthog.args.OptionalCaptureArgs.properties` - -```python -properties: NotRequired[Optional[Dict[str, Any]]] -``` - -#### `posthog.args.OptionalCaptureArgs.send_feature_flags` - -```python -send_feature_flags: NotRequired[Optional[Union[bool, SendFeatureFlagsOptions]]] -``` - -#### `posthog.args.OptionalCaptureArgs.timestamp` - -```python -timestamp: NotRequired[Optional[Union[datetime, str]]] -``` - -#### `posthog.args.OptionalCaptureArgs.uuid` - -```python -uuid: NotRequired[Optional[Union[str, UUID]]] -``` - -### `FeatureFlagEvaluations` - -```python -FeatureFlagEvaluations(host: _FeatureFlagEvaluationsHost, distinct_id: str, flags: Dict[str, _EvaluatedFlagRecord], groups: Optional[Mapping[str, Union[str, int]]] = None, disable_geoip: Optional[bool] = None, request_id: Optional[str] = None, evaluated_at: Optional[int] = None, errors_while_computing: bool = False, quota_limited: bool = False, minimal_flag_called_events: bool = False, accessed: Optional[Set[str]] = None, unresolved_flags: Optional[Mapping[str, UnresolvedFlagReason]] = None) -> None -``` - -A point-in-time snapshot of feature flag evaluations for a single distinct_id. - -Returned by `Client.evaluate_flags` — branch on `is_enabled` / -`get_flag` and pass the same object to `Client.capture` via the -`flags` option so the captured event carries the exact flag values the code -branched on. - -Example:: - - flags = posthog.evaluate_flags(distinct_id, person_properties={"plan": "enterprise"}) - if flags.is_enabled("new-dashboard"): - render_new_dashboard() - posthog.capture("page_viewed", distinct_id=distinct_id, flags=flags) - -To narrow the set of flags that get attached to a captured event, use the in-memory -helpers `only` and `only_accessed`. To narrow the set of flags requested -from the server in the first place, pass `flag_keys` to `Client.evaluate_flags`. - -**Functions:** - -Name | Description ----- | ----------- -`get_flag` | Return the flag value. Fires `$feature_flag_called` on first access. -`get_flag_payload` | Return the payload associated with a flag. -`is_enabled` | Return whether the flag is enabled. Fires `$feature_flag_called` on the -`only` | Return a filtered copy containing only flags with the given keys. Keys that -`only_accessed` | Return a filtered copy containing only flags accessed via `is_enabled` - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`keys` | `List[str]` | Return the flag keys that are part of this evaluation. -`unresolved_flags` | `Mapping[str, UnresolvedFlagReason]` | Return a read-only mapping of flag key to the reason local evaluation - -Internal — instances are created by the SDK via `Client.evaluate_flags()`. - -#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.get_flag` - -```python -get_flag(key: str, *, send_feature_flag_events: bool = True) -> Optional[FlagValue] -``` - -Return the flag value. Fires `$feature_flag_called` on first access. - -Returns the variant string for multivariate flags, `True` for enabled flags -without a variant, `False` for disabled flags, and `None` for flags that -were not returned by the evaluation. - -Pass `send_feature_flag_events=False` to skip the event for this read, as -with `is_enabled`. - -#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.get_flag_payload` - -```python -get_flag_payload(key: str) -> Optional[Any] -``` - -Return the payload associated with a flag. - -Does not count as an access for `only_accessed` and does not fire any event. - -#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.is_enabled` - -```python -is_enabled(key: str, default_value: bool = False, *, send_feature_flag_events: bool = True) -> bool -``` - -Return whether the flag is enabled. Fires `$feature_flag_called` on the -first access per (distinct_id, flag, value) tuple, deduped via the SDK's cache. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The feature flag key. | *required* -`default_value` | `bool` | Returned when the flag has no value in this evaluation — the key was not part of the evaluated set, or the evaluation itself came back empty (failed `/flags` request, quota limit, or no resolvable `distinct_id`). Defaults to `False`. | `False` -`send_feature_flag_events` | `bool` | Whether this read sends `$feature_flag_called`. When `False`, no event is sent and the dedupe cache is left alone, but the key still counts as accessed for `only_accessed`. | `True` - -A flag that has a value always wins over `default_value`, including a -disabled flag, which returns `False` even when `default_value=True`. - -#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.keys` - -```python -keys: List[str] -``` - -Return the flag keys that are part of this evaluation. - -#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.only` - -```python -only(keys: List[str]) -> FeatureFlagEvaluations -``` - -Return a filtered copy containing only flags with the given keys. Keys that -are not present in the evaluation are dropped and logged as a warning. - -#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.only_accessed` - -```python -only_accessed() -> FeatureFlagEvaluations -``` - -Return a filtered copy containing only flags accessed via `is_enabled` -or `get_flag` before this call. - -Order-dependent: if nothing has been accessed yet, the returned snapshot is -empty. The method honors its name — pre-access if you want a populated result. - -#### `posthog.feature_flag_evaluations.FeatureFlagEvaluations.unresolved_flags` - -```python -unresolved_flags: Mapping[str, UnresolvedFlagReason] -``` - -Return a read-only mapping of flag key to the reason local evaluation -left it without a value. - -A flag is listed when it has a loaded local definition, was in the requested -scope, was inconclusive locally, and no remote value filled the gap. Listed -flags are not part of `keys`, and reading one reports the -`local_evaluation_inconclusive` error instead of `flag_missing`. Reading -this mapping does not count as an access and does not fire any event. -Snapshots returned by `only` and `only_accessed`, and by -`AsyncClient.evaluate_flags()`, which evaluates only remotely, carry no -entries. - -### `FeatureFlagResult` - -```python -FeatureFlagResult(key: str, enabled: bool, variant: Optional[str], payload: Optional[Any], reason: Optional[str]) -> None -``` - -The result of calling a feature flag which includes the flag result, variant, and payload. - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`key` | `str` | The unique identifier of the feature flag. -`enabled` | `bool` | Whether the feature flag is enabled for the current context. -`variant` | `Optional[str]` | The variant value if the flag is enabled and has variants, None otherwise. -`payload` | `Optional[Any]` | Additional data associated with the feature flag, if any. -`reason` | `Optional[str]` | A description of why the flag was enabled or disabled, if available. - -**Functions:** - -Name | Description ----- | ----------- -`from_flag_details` | Create a FeatureFlagResult from a FeatureFlag object. -`from_value_and_payload` | Creates a FeatureFlagResult from a flag value and payload. -`get_value` | Returns the value of the flag. This is the variant if it exists, otherwise the enabled value. - -#### `posthog.types.FeatureFlagResult.enabled` - -```python -enabled: bool -``` - -#### `posthog.types.FeatureFlagResult.from_flag_details` - -```python -from_flag_details(details: Union[FeatureFlag, None], override_match_value: Optional[FlagValue] = None) -> FeatureFlagResult | None -``` - -Create a FeatureFlagResult from a FeatureFlag object. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`details` | `Union[FeatureFlag, None]` | The FeatureFlag object to convert. | *required* -`override_match_value` | `Optional[FlagValue]` | If provided, this value will be used to populate the enabled and variant fields instead of the values from the FeatureFlag. | `None` - -**Returns:** - -Type | Description ----- | ----------- -`FeatureFlagResult \| None` | FeatureFlagResult | None: A new FeatureFlagResult instance, or None if details is None. - -#### `posthog.types.FeatureFlagResult.from_value_and_payload` - -```python -from_value_and_payload(key: str, value: Union[FlagValue, None], payload: Any) -> Union[FeatureFlagResult, None] -``` - -Creates a FeatureFlagResult from a flag value and payload. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`key` | `str` | The unique identifier of the feature flag. | *required* -`value` | `Union[FlagValue, None]` | The value of the flag (string variant or boolean). | *required* -`payload` | `Any` | Additional data associated with the feature flag. | *required* - -**Returns:** - -Type | Description ----- | ----------- -`Union[FeatureFlagResult, None]` | Union[FeatureFlagResult, None]: A new FeatureFlagResult instance, or None if value is None. - -#### `posthog.types.FeatureFlagResult.get_value` - -```python -get_value() -> FlagValue -``` - -Returns the value of the flag. This is the variant if it exists, otherwise the enabled value. -This is the value we report as `$feature_flag_response` in the `$feature_flag_called` event. - -**Returns:** - -Name | Type | Description ----- | ---- | ----------- -`FlagValue` | `FlagValue` | Either a string variant or boolean value representing the flag's state. - -#### `posthog.types.FeatureFlagResult.key` - -```python -key: str -``` - -#### `posthog.types.FeatureFlagResult.payload` - -```python -payload: Optional[Any] -``` - -#### `posthog.types.FeatureFlagResult.reason` - -```python -reason: Optional[str] -``` - -#### `posthog.types.FeatureFlagResult.variant` - -```python -variant: Optional[str] -``` - -### `FeatureFlag` - -```python -FeatureFlag(key: str, enabled: bool, variant: Optional[str], reason: Optional[FlagReason], metadata: Union[FlagMetadata, LegacyFlagMetadata]) -> None -``` - -Detailed feature flag evaluation returned by the flags API. - -`enabled`, `variant` and `metadata.payload` are the value rendered the way -responses before `/flags?v=4` carried it, which the legacy getters return. - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`key` | `str` | Feature flag key. -`enabled` | `bool` | Whether the flag is enabled for the evaluated user or group. -`variant` | `Optional[str]` | Variant key for multivariate flags, otherwise `None`. -`reason` | `Optional[FlagReason]` | Optional reason metadata explaining the result. -`metadata` | `Union[FlagMetadata, LegacyFlagMetadata]` | Payload and other metadata returned by the API. - -**Functions:** - -Name | Description ----- | ----------- -`from_json` | Parse one flag record of a `/flags` response. -`from_value_and_payload` | Build a flag from its value and payload. A string value is a variant, so the flag is enabled. -`get_value` | Return the variant for a multivariate flag, otherwise whether the flag is enabled. - -#### `posthog.types.FeatureFlag.enabled` - -```python -enabled: bool -``` - -#### `posthog.types.FeatureFlag.from_json` - -```python -from_json(resp: Any) -> FeatureFlag -``` - -Parse one flag record of a `/flags` response. - -A record with a `value` member is a `/flags?v=4` record. A record -without one comes from an older server: its value is `variant`, or -`enabled` when there is no variant, and its config version is `1`. - -#### `posthog.types.FeatureFlag.from_value_and_payload` - -```python -from_value_and_payload(key: str, value: FlagValue, payload: Any) -> FeatureFlag -``` - -Build a flag from its value and payload. A string value is a variant, so the flag is enabled. - -#### `posthog.types.FeatureFlag.get_value` - -```python -get_value() -> FlagValue -``` - -Return the variant for a multivariate flag, otherwise whether the flag is enabled. - -#### `posthog.types.FeatureFlag.key` - -```python -key: str -``` - -#### `posthog.types.FeatureFlag.metadata` - -```python -metadata: Union[FlagMetadata, LegacyFlagMetadata] -``` - -#### `posthog.types.FeatureFlag.reason` - -```python -reason: Optional[FlagReason] -``` - -#### `posthog.types.FeatureFlag.variant` - -```python -variant: Optional[str] -``` - -### `FlagsAndPayloads` - -Bases: `TypedDict` - -Feature flag values and payloads keyed by feature flag key. - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`featureFlags` | `Optional[dict[str, FlagValue]]` | Each flag's value, keyed by flag key. -`featureFlagPayloads` | `Optional[dict[str, Any]]` | Each flag's payload, keyed by flag key. - -#### `posthog.types.FlagsAndPayloads.featureFlagPayloads` - -```python -featureFlagPayloads: Optional[dict[str, Any]] -``` - -#### `posthog.types.FlagsAndPayloads.featureFlags` - -```python -featureFlags: Optional[dict[str, FlagValue]] -``` - -### `UnresolvedFlagReason` - -Bases: `str`, `Enum` - -Why local evaluation left a flag without a value. - -Reported by `FeatureFlagEvaluations.unresolved_flags`. Inheriting from -`str` keeps the members directly comparable to their string values. When more -than one cause applies, the first reason in the order below is reported. - -
-Values - -EXPERIENCE_CONTINUITY: The flag has experience continuity enabled, which - local evaluation never resolves. Change the flag to fix it. -UNSUPPORTED_DEFINITION: The flag uses something the local evaluator does not - support, such as a static cohort, an unknown operator, a malformed value, - a dependency with no loaded definition or a circular dependency. Change - the flag or upgrade the SDK to fix it. -UNRESOLVED_DEPENDENCY: A flag this flag depends on has a loaded definition - but was unresolved. -MISSING_CONTEXT: The call did not pass a property, group property or device - id the flag needs, or passed it in an unusable form. Pass it to fix it. - -
- -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`EXPERIENCE_CONTINUITY` | | -`MISSING_CONTEXT` | | -`UNRESOLVED_DEPENDENCY` | | -`UNSUPPORTED_DEFINITION` | | - -#### `posthog.types.UnresolvedFlagReason.EXPERIENCE_CONTINUITY` - -```python -EXPERIENCE_CONTINUITY = 'experience_continuity' -``` - -#### `posthog.types.UnresolvedFlagReason.MISSING_CONTEXT` - -```python -MISSING_CONTEXT = 'missing_context' -``` - -#### `posthog.types.UnresolvedFlagReason.UNRESOLVED_DEPENDENCY` - -```python -UNRESOLVED_DEPENDENCY = 'unresolved_dependency' -``` - -#### `posthog.types.UnresolvedFlagReason.UNSUPPORTED_DEFINITION` - -```python -UNSUPPORTED_DEFINITION = 'unsupported_definition' -``` - -### `InconclusiveMatchError` - -```python -InconclusiveMatchError(*args, reason: Optional[UnresolvedFlagReason] = None) -``` - -Bases: `Exception` - -Raised when local evaluation can't determine a flag's value. `reason` says -why, as reported by `FeatureFlagEvaluations.unresolved_flags`. - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`reason` | `UnresolvedFlagReason` | Why local evaluation couldn't resolve the flag. - -#### `posthog.feature_flags.InconclusiveMatchError.reason` - -```python -reason: UnresolvedFlagReason = UnresolvedFlagReason.UNSUPPORTED_DEFINITION -``` - -### `RequiresServerEvaluation` - -```python -RequiresServerEvaluation(*args, reason: Optional[UnresolvedFlagReason] = None) -``` - -Bases: `Exception` - -Raised when feature flag evaluation requires server-side data that is not -available locally (e.g., static cohorts). - -This error should propagate immediately to trigger API fallback, unlike -InconclusiveMatchError which allows trying other conditions. - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`reason` | `UnresolvedFlagReason` | Why the flag needs server-side evaluation. - -#### `posthog.feature_flags.RequiresServerEvaluation.reason` - -```python -reason: UnresolvedFlagReason = UnresolvedFlagReason.UNSUPPORTED_DEFINITION -``` - -### `Span` - -A handle to a span. - -Every method is safe to call on any handle, including after `end()` and -on the inert handles returned when tracing is off, so calling code never -branches on whether tracing is running. Entering a handle (`with span:`) -makes it the active span for the block and ends it on exit, recording an -`Exception` raised inside it. A `BaseException` that is not an -`Exception` (`GeneratorExit`, `CancelledError`, `KeyboardInterrupt`) -still ends the span but is not recorded as a failure. - -**Functions:** - -Name | Description ----- | ----------- -`add_event` | Add a timestamped event to the span. Ignored after `end()`. -`end` | End the span and queue it for export. Idempotent. -`record_exception` | Record an exception as an `exception` event and mark the span `error`. -`set_attribute` | Set one attribute on the span; last write wins. Ignored after `end()`. -`set_attributes` | Set several attributes at once; last write wins per key. Ignored after `end()`. -`set_status` | Set the span status to `"ok"` or `"error"`. Ignored after `end()`. -`traceparent` | The W3C `traceparent` header value to propagate, or `None`. -`tracestate` | The W3C `tracestate` header value to propagate, or `None`. -`update_name` | Rename the span, for a name only known after it started. Ignored after `end()`. - -#### `posthog.tracing.span.Span.add_event` - -```python -add_event(name: str, attributes: Optional[Mapping[str, Any]] = None, timestamp: Union[datetime, float, None] = None) -> Span -``` - -Add a timestamped event to the span. Ignored after `end()`. - -`timestamp` defaults to now; it accepts a `datetime`, or seconds -since the epoch as a `float` or `int`. Returns the span, so calls -chain. - -**Examples:** - -```python -span.add_event("cache.miss", {"key": "user:42"}) -``` - -#### `posthog.tracing.span.Span.end` - -```python -end(end_time: Union[datetime, float, None] = None) -> None -``` - -End the span and queue it for export. Idempotent. - -`end_time` defaults to now; it accepts a `datetime`, or seconds -since the epoch as a `float` or `int`, and is never earlier than -the start. Later calls, and later mutations, no-op. `with span:` -ends the span on exit, so call this only for spans started manually. - -**Examples:** - -```python -span = posthog.start_span("job") -try: - run_job() -finally: - span.end() -``` - -#### `posthog.tracing.span.Span.record_exception` - -```python -record_exception(exception: BaseException) -> Span -``` - -Record an exception as an `exception` event and mark the span `error`. - -Ignored after `end()`. The event carries `exception.type`, -`exception.message` and, for a raised exception, -`exception.stacktrace` (its last `max_attribute_value_length` -characters, so a chained traceback keeps the outermost exception and -may lose its cause). Returns the span, so calls chain. Inside -`with span:` a raised `Exception` is recorded automatically, so -this is for exceptions that are caught and handled. - -**Examples:** - -```python -try: - charge(card) -except PaymentError as e: - span.record_exception(e) -``` - -#### `posthog.tracing.span.Span.set_attribute` - -```python -set_attribute(key: str, value: Any) -> Span -``` - -Set one attribute on the span; last write wins. Ignored after `end()`. - -Returns the span, so calls chain. Prefer primitive values: strings, -booleans, integers and floats keep their type. Lists and mappings are -sent as OTLP arrays and maps, but PostHog stores them as serialized -strings. `None` removes the key; anything else is stringified. - -**Examples:** - -```python -span.set_attribute("http.status_code", 200).set_attribute("cache.hit", True) -``` - -#### `posthog.tracing.span.Span.set_attributes` - -```python -set_attributes(attributes: Mapping[str, Any]) -> Span -``` - -Set several attributes at once; last write wins per key. Ignored after `end()`. - -Returns the span, so calls chain. Same encoding as `set_attribute()`. - -**Examples:** - -```python -span.set_attributes({"db.system": "postgresql", "db.rows": 12}) -``` - -#### `posthog.tracing.span.Span.set_status` - -```python -set_status(code: str, message: Optional[str] = None) -> Span -``` - -Set the span status to `"ok"` or `"error"`. Ignored after `end()`. - -Unset by default. Any other `code` is ignored. `ok` is final for the -scoped form: an `Exception` raised inside `with span:` does not override -it. Returns the span, so calls chain. - -**Examples:** - -```python -span.set_status("error", "upstream timed out") -``` - -#### `posthog.tracing.span.Span.traceparent` - -```python -traceparent() -> Optional[str] -``` - -The W3C `traceparent` header value to propagate, or `None`. - -#### `posthog.tracing.span.Span.tracestate` - -```python -tracestate() -> Optional[str] -``` - -The W3C `tracestate` header value to propagate, or `None`. - -#### `posthog.tracing.span.Span.update_name` - -```python -update_name(name: str) -> Span -``` - -Rename the span, for a name only known after it started. Ignored after `end()`. - -Returns the span, so calls chain. - -**Examples:** - -```python -span = posthog.start_span("http.request") -span.update_name(f"{request.method} {route.pattern}") -``` - -### `Emails` - -```python -Emails(secret_key: Optional[str], *, host: Optional[str] = None, timeout: float = 15, max_retries: int = 2, disabled: bool = False, session: Optional[requests.Session] = None) -> None -``` - -Bases: `_EmailsBase` - -Sends transactional email from PostHog email templates. - -`host` is your PostHog app host. PostHog Cloud ingestion hosts map to the -app host, so `us.i.posthog.com` sends to `us.posthog.com`. Any other -host is used as given. - -Each send retries network errors, rate limits and an unavailable service up -to `max_retries` times, with the same idempotency key. Set -`max_retries=0` when a job runner already retries the send. `timeout` -is in seconds, per request. Pass `session` to send through your own -`requests.Session`, for example a test double. - -**Functions:** - -Name | Description ----- | ----------- -`close` | Close the HTTP connections this client opened. A `session` you passed in stays open. -`get` | Read what happened to an email that `send` queued. -`send` | Queue one email from a transactional email template. - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`disabled` | | -`host` | | -`max_retries` | | -`secret_key` | | -`timeout` | | - -#### `posthog.emails.Emails.close` - -```python -close() -> None -``` - -Close the HTTP connections this client opened. A `session` you passed in stays open. - -#### `posthog.emails.Emails.disabled` - -```python -disabled = disabled -``` - -#### `posthog.emails.Emails.get` - -```python -get(message_id: Union[str, UUID]) -> EmailStatus -``` - -Read what happened to an email that `send` queued. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`message_id` | `Union[str, UUID]` | The `id` of the `SendEmailResult` that `send` returned. | *required* - -**Returns:** - -Type | Description ----- | ----------- -`EmailStatus` | The email's latest status, for 30 days after the send. - -**Raises:** - -Type | Description ----- | ----------- -`EmailError` | PostHog did not answer with a status. `code` is `not_found` for an unknown ID, an email older than 30 days, or for a few seconds after the send. -`ValueError` | `message_id` is not a message ID. - -**Examples:** - -```python -result = emails.send(to="user@example.com", template_id="...") -status = emails.get(result.id) -if status.status == "bounced": - print(status.reason) -``` - -#### `posthog.emails.Emails.host` - -```python -host = determine_app_host(host) -``` - -#### `posthog.emails.Emails.max_retries` - -```python -max_retries = max_retries -``` - -#### `posthog.emails.Emails.secret_key` - -```python -secret_key = secret_key.strip() if isinstance(secret_key, str) else None -``` - -#### `posthog.emails.Emails.send` - -```python -send(*, to: str, template_id: Union[str, UUID], template_data: Optional[Mapping[str, Any]] = None, distinct_id: Optional[str] = None, idempotency_key: Optional[str] = None, max_retries: Optional[int] = None) -> SendEmailResult -``` - -Queue one email from a transactional email template. - -**Parameters:** - -Name | Type | Description | Default ----- | ---- | ----------- | ------- -`to` | `str` | One recipient email address. | *required* -`template_id` | `Union[str, UUID]` | The UUID of a template made transactional in PostHog. | *required* -`template_data` | `Optional[Mapping[str, Any]]` | Values for the template's `{{ variables.* }}` placeholders. Values are encoded the way capture encodes properties. | `None` -`distinct_id` | `Optional[str]` | The recipient's distinct ID in your product. The email's events go to this person. Without it, they go to a person whose distinct ID is the email address. | `None` -`idempotency_key` | `Optional[str]` | 1 to 255 visible ASCII characters. A send with a key that PostHog has already accepted queues nothing and returns the first send's result. Without it, the client makes a key that covers its own retries. Pass a key derived from what the email is for, such as `welcome:user_123`, to cover retries outside this call, too. | `None` -`max_retries` | `Optional[int]` | Overrides the client's maximum number of retries for this send. `0` turns retries off. | `None` - -**Returns:** - -Type | Description ----- | ----------- -`SendEmailResult` | The queued email. A result means PostHog queued the email, not that -`SendEmailResult` | it was delivered. - -**Raises:** - -Type | Description ----- | ----------- -`EmailError` | PostHog did not queue the email, or the client could not tell whether it did. -`(TypeError, ValueError)` | An argument has the wrong type or format. - -**Examples:** - -```python -emails.send( - to="user@example.com", - template_id="0190f6d2-7c1e-7b3a-9f5e-1c2d3e4f5a6b", - template_data={"name": "Ada"}, - distinct_id="user_123", - idempotency_key="welcome:user_123", -) -``` - -#### `posthog.emails.Emails.timeout` - -```python -timeout = timeout -``` - -### `EmailError` - -```python -EmailError(*, name: str, code: str, message: str, status_code: Optional[int] = None, why: Optional[str] = None, fix: Optional[str] = None, docs_url: Optional[str] = None, field: Optional[str] = None, retry_after: Optional[float] = None) -> None -``` - -Bases: `Exception` - -PostHog did not queue the email, or the client could not tell whether it did. - -Branch on `code`, PostHog's stable error code, or on `name`, a coarse -category of the failure. The message never contains the secret key or the -request body. - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`name` | | A coarse category of the failure. -`code` | | PostHog's stable error code. -`message` | | What went wrong. It never contains the secret key or the request body. -`status_code` | | The HTTP status of the response, or `None` when none arrived. -`why` | | PostHog's explanation of the failure, when it gave one. -`fix` | | What to change to fix it, when PostHog said. -`docs_url` | | A docs page about the error, when PostHog gave one. -`field` | | The request field the error is about, when there is one. -`retry_after` | | Seconds to wait before retrying, when PostHog said. - -#### `posthog.emails.EmailError.code` - -```python -code = code -``` - -#### `posthog.emails.EmailError.docs_url` - -```python -docs_url = docs_url -``` - -#### `posthog.emails.EmailError.field` - -```python -field = field -``` - -#### `posthog.emails.EmailError.fix` - -```python -fix = fix -``` - -#### `posthog.emails.EmailError.message` - -```python -message = message -``` - -#### `posthog.emails.EmailError.name` - -```python -name = name -``` - -#### `posthog.emails.EmailError.retry_after` - -```python -retry_after = retry_after -``` - -#### `posthog.emails.EmailError.status_code` - -```python -status_code = status_code -``` - -#### `posthog.emails.EmailError.why` - -```python -why = why -``` - -### `SendEmailResult` - -```python -SendEmailResult(id: str, replayed: bool = False, warnings: tuple[EmailWarning, ...] = ()) -> None -``` - -The queued email. `id` is its message ID. - -**Attributes:** - -Name | Type | Description ----- | ---- | ----------- -`id` | `str` | The email's message ID. -`replayed` | `bool` | `True` when the response says PostHog replayed an earlier send for the same `idempotency_key` instead of sending again. -`warnings` | `tuple[EmailWarning, ...]` | Warnings PostHog returned about the request. - -#### `posthog.emails.SendEmailResult.id` - -```python -id: str -``` - -#### `posthog.emails.SendEmailResult.replayed` - -```python -replayed: bool = False -``` - -#### `posthog.emails.SendEmailResult.warnings` - -```python -warnings: tuple[EmailWarning, ...] = () -```