---
title: Python
sidebarTitle: Python
sidebar: Docs
showTitle: true
github: 'https://github.com/PostHog/posthog-python'
icon: >-
  https://res.cloudinary.com/dmukukwp6/image/upload/posthog.com/contents/images/docs/integrate/python.svg
features:
  eventCapture: true
  userIdentification: true
  featureFlags: true
  groupAnalytics: true
  surveys: false
  llmAnalytics: true
  errorTracking: true
---

The Python SDK makes it easy to capture events, evaluate feature flags, track errors, and more in your Python apps.

## Installation

import PythonInstall from '../../integrate/_snippets/install-python.mdx'

<PythonInstall />

## Capturing events

import PythonSendEvents from '../../integrate/send-events/_snippets/send-events-python.mdx'

<PythonSendEvents />

## Person profiles and properties

The Python SDK captures identified events if the current context is identified or if you pass a distinct ID explicitly. These create [person profiles](/docs/data/persons). To set [person properties](/docs/data/user-properties) in these profiles, include them when capturing an event:

```python
# Passing a distinct id explicitly
posthog.capture(
    'event_name',
    distinct_id='user-distinct-id',
    properties={
        '$set': {'name': 'Max Hedgehog'},
        '$set_once': {'initial_url': '/blog'}
    }
)

# Using contexts
from posthog import new_context, identify_context
with new_context():
    identify_context('user-distinct-id')
    posthog.capture('event_name')
```

For more details on the difference between `$set` and `$set_once`, see our [person properties docs](/docs/data/user-properties#what-is-the-difference-between-set-and-set_once).

To capture [anonymous events](/docs/data/anonymous-vs-identified-events) without person profiles, set the event's `$process_person_profile` property to `False`. Events captured with no context or explicit distinct_id are marked as personless, and will have an auto-generated distinct_id:

```python
posthog.capture(
    event='event_name',
    properties={
        '$process_person_profile': False
    }
)
```

## Alias

Sometimes, you want to assign multiple distinct IDs to a single user. This is helpful when your primary distinct ID is inaccessible. For example, if a distinct ID used on the frontend is not available in your backend.

In this case, you can use `alias` to assign another distinct ID to the same user.

```python
posthog.alias(previous_id='distinct_id', distinct_id='alias_id')
```

We strongly recommend reading our docs on [alias](/docs/product-analytics/identify#alias-assigning-multiple-distinct-ids-to-the-same-user) to best understand how to correctly use this method.

## Contexts

import Contexts from './_snippets/contexts.mdx'

<Contexts />

## Group analytics

Group analytics allows you to associate an event with a group (e.g. teams, organizations, etc.). Read the [Group Analytics](/docs/user-guides/group-analytics) guide for more information.

> **Note:** This is a paid feature and is not available on the open-source or free cloud plan. Learn more on our [pricing page](/pricing).

To capture an event and associate it with a group:

```python
posthog.capture('some_event', groups={'company': 'company_id_in_your_db'})
```

To update properties on a group:

```python
posthog.group_identify('company', 'company_id_in_your_db', {
    'name': 'Awesome Inc.',
    'employees': 11
})
```

The `name` is a special property which is used in the PostHog UI for the name of the group. If you don't specify a `name` property, the group ID will be used instead.

## Feature flags

import FeatureFlagsLibsIntro from "../_snippets/feature-flags-libs-intro.mdx"

<FeatureFlagsLibsIntro />

import PythonFeatureFlagsCode from '../../integrate/feature-flags-code/_snippets/feature-flags-code-python.mdx'

<PythonFeatureFlagsCode />

### Local evaluation

import LocalEvaluationIntro from "../../feature-flags/snippets/local-evaluation-intro.mdx"

<LocalEvaluationIntro />

For details on how to implement local evaluation, see our [local evaluation guide](/docs/feature-flags/local-evaluation).

## Experiments (A/B tests)

Since [experiments](/docs/experiments/manual) use feature flags, the code for running an experiment is very similar to the feature flags code:

```python
variant = posthog.get_feature_flag('experiment-feature-flag-key', 'user_distinct_id')

if variant == 'variant-name':
    # Do something
```

It's also possible to [run experiments without using feature flags](/docs/experiments/running-experiments-without-feature-flags).

## LLM analytics

Our Python SDK includes a built-in LLM analytics feature. It enables you to capture LLM usage, performance, and more. Check out our [analytics docs](/docs/llm-analytics) for more details on setting it up.

## Error tracking

You can [autocapture exceptions](/docs/error-tracking/installation) by setting the `enable_exception_autocapture` argument to `True` when initializing the PostHog client.

```python
from posthog import Posthog

posthog = Posthog("<ph_project_api_key>", enable_exception_autocapture=True, ...)
```

You can also manually capture exceptions using the `capture_exception` method:

```python
posthog.capture_exception(e, 'user_distinct_id', properties=additional_properties)
```

Contexts automatically capture exceptions thrown inside them, unless disable it by passing `capture_exceptions=False` to `new_context()`.

## GeoIP properties

Before posthog-python v3.0, we added GeoIP properties to all incoming events by default. We also used these properties for feature flag evaluation, based on the IP address of the request. This isn't ideal since they are created based on your server IP address, rather than the user's, leading to incorrect location resolution.

As of posthog-python v3.0, the default now is to disregard the server IP, not add the GeoIP properties, and not use the values for feature flag evaluations.

You can go back to previous behaviour by doing setting the `disable_geoip` argument in your initialization to `False`:

```python
posthog = Posthog('api_key', disable_geoip=False)
```

The list of properties that this overrides:

1. `$geoip_city_name`
2. `$geoip_country_name`
3. `$geoip_country_code`
4. `$geoip_continent_name`
5. `$geoip_continent_code`
6. `$geoip_postal_code`
7. `$geoip_time_zone`

You can also explicitly chose to enable or disable GeoIP for a single capture request like so:

```python
posthog.capture('test_event', disable_geoip=True|False)
```

## Debug mode 

If you're not seeing the expected events being captured, the feature flags being evaluated, or the surveys being shown, you can enable debug mode to see what's happening. 

You can enable debug mode by setting the `debug` option to `True` in the `PostHog` object. This will enable verbose logs about the inner workings of the SDK.

```python
posthog.debug = True # +
```

## Disabling requests during tests

You can disable requests during tests by setting the `disabled` option to `True` in the `PostHog` object. This means no events will be captured or no requests will be sent to PostHog.

```python
if settings.TEST:
    posthog.disabled = True
```

## Historical migrations

You can use the Python or Node SDK to run [historical migrations](/docs/migrate) of data into PostHog. To do so, set the `historical_migration` option to `true` when initializing the client.

import SDKMigration from "../../migrate/_snippets/sdk-migration.mdx"

<SDKMigration />

## Serverless environments (Render/Lambda/...)

By default, the library buffers events before sending them to the capture endpoint, for better performance. This can lead to lost events in serverless environments, if the Python process is terminated by the platform before the buffer is fully flushed. To avoid this, you can either:

- Ensure that `posthog.shutdown()` is called after processing every request by adding a middleware to your server. This allows `posthog.capture()` to remain asynchronous for better performance. `posthog.shutdown()` is blocking.
- Enable the `sync_mode` option when initializing the client, so that all calls to `posthog.capture()` become synchronous.

## Django

See our [Django docs](/docs/libraries/django) for how to set up PostHog in Django. Our library includes a [contexts middleware](/docs/libraries/django#django-contexts-middleware) that can automatically capture distinct IDs, session IDs, and other properties you can set up with tags.

## Alternative name

As our open source project [PostHog](https://github.com/PostHog/posthog) shares the same module name, we created a special `posthoganalytics` package, mostly for internal use to avoid module collision. It is the exact same.

## Thank you

This library is largely based on the `analytics-python` package.
