diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index a18238c43fc..4405b6caa5d 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -1,3 +1,7 @@
+# hadolint global ignore=DL3004
+
+# hadolint doesn't like changes to this file, but it is only used for local dev
+
# Defines the environment you're dropped into with codespaces
# I've take
# https://github.com/microsoft/vscode-dev-containers/blob/main/containers/python-3/.devcontainer/Dockerfile
@@ -7,7 +11,7 @@
# experience as rich as possible. Perhaps later down the line it might be worth
# rolling our own
#
-FROM mcr.microsoft.com/vscode/devcontainers/python:3.10-bullseye
+FROM mcr.microsoft.com/vscode/devcontainers/python:3.11-bullseye
# Make sure all exit codes on pipes cause failures
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
diff --git a/.github/actions/run-backend-tests/action.yml b/.github/actions/run-backend-tests/action.yml
index c1d46516616..39cba842f23 100644
--- a/.github/actions/run-backend-tests/action.yml
+++ b/.github/actions/run-backend-tests/action.yml
@@ -6,7 +6,7 @@ name: Run Django tests
inputs:
python-version:
required: true
- description: Python version, e.g. 3.10.10
+ description: Python version, e.g. 3.11.9
clickhouse-server-image:
required: true
description: ClickHouse server image tag, e.g. clickhouse/clickhouse-server:latest
diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml
index bd50811fae6..9478b7d2f8c 100644
--- a/.github/workflows/benchmark.yml
+++ b/.github/workflows/benchmark.yml
@@ -54,7 +54,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
- python-version: 3.10.10
+ python-version: 3.11.9
cache: 'pip'
cache-dependency-path: '**/requirements*.txt'
token: ${{ secrets.POSTHOG_BOT_GITHUB_TOKEN }}
diff --git a/.github/workflows/ci-backend-update-test-timing.yml b/.github/workflows/ci-backend-update-test-timing.yml
index a2082f6b989..01ad7d33ce3 100644
--- a/.github/workflows/ci-backend-update-test-timing.yml
+++ b/.github/workflows/ci-backend-update-test-timing.yml
@@ -28,7 +28,7 @@ jobs:
concurrency: 1
group: 1
token: ${{ secrets.POSTHOG_BOT_GITHUB_TOKEN }}
- python-version: '3.10.10'
+ python-version: '3.11.9'
clickhouse-server-image: 'clickhouse/clickhouse-server:23.12.5.81-alpine'
segment: 'FOSS'
person-on-events: false
diff --git a/.github/workflows/ci-backend.yml b/.github/workflows/ci-backend.yml
index 9e2aae60c76..b757f69c8f8 100644
--- a/.github/workflows/ci-backend.yml
+++ b/.github/workflows/ci-backend.yml
@@ -108,7 +108,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
- python-version: 3.10.10
+ python-version: 3.11.9
cache: 'pip'
cache-dependency-path: '**/requirements*.txt'
token: ${{ secrets.POSTHOG_BOT_GITHUB_TOKEN }}
@@ -163,7 +163,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
- python-version: 3.10.10
+ python-version: 3.11.9
cache: 'pip'
cache-dependency-path: '**/requirements*.txt'
token: ${{ secrets.POSTHOG_BOT_GITHUB_TOKEN }}
@@ -177,22 +177,21 @@ jobs:
sudo apt-get install libxml2-dev libxmlsec1-dev libxmlsec1-openssl
# First running migrations from master, to simulate the real-world scenario
-
- - name: Checkout master
- uses: actions/checkout@v3
- with:
- ref: master
-
- - name: Install python dependencies for master
- run: |
- uv pip install --system -r requirements.txt -r requirements-dev.txt
-
- - name: Run migrations up to master
- run: |
- python manage.py migrate
+ # Commented out to move to Python 3.11. Uncomment after deploy.
+ # - name: Checkout master
+ # uses: actions/checkout@v3
+ # with:
+ # ref: master
+ #
+ # - name: Install python dependencies for master
+ # run: |
+ # uv pip install --system -r requirements.txt -r requirements-dev.txt
+ #
+ # - name: Run migrations up to master
+ # run: |
+ # python manage.py migrate
# Now we can consider this PR's migrations
-
- name: Checkout this PR
uses: actions/checkout@v3
@@ -204,22 +203,24 @@ jobs:
run: |
python manage.py migrate
- - name: Check migrations
- run: |
- python manage.py makemigrations --check --dry-run
- git fetch origin master
- # `git diff --name-only` returns a list of files that were changed - added OR deleted OR modified
- # With `--name-status` we get the same, but including a column for status, respectively: A, D, M
- # In this check we exclusively care about files that were
- # added (A) in posthog/migrations/. We also want to ignore
- # initial migrations (0001_*) as these are guaranteed to be
- # run on initial setup where there is no data.
- git diff --name-status origin/master..HEAD | grep "A\sposthog/migrations/" | awk '{print $2}' | grep -v migrations/0001_ | python manage.py test_migrations_are_safe
-
- - name: Check CH migrations
- run: |
- # Same as above, except now for CH looking at files that were added in posthog/clickhouse/migrations/
- git diff --name-status origin/master..HEAD | grep "A\sposthog/clickhouse/migrations/" | awk '{print $2}' | python manage.py test_ch_migrations_are_safe
+ # Commented out to move to Python 3.11. Uncomment after deploy.
+ #
+ # - name: Check migrations
+ # run: |
+ # python manage.py makemigrations --check --dry-run
+ # git fetch origin master
+ # # `git diff --name-only` returns a list of files that were changed - added OR deleted OR modified
+ # # With `--name-status` we get the same, but including a column for status, respectively: A, D, M
+ # # In this check we exclusively care about files that were
+ # # added (A) in posthog/migrations/. We also want to ignore
+ # # initial migrations (0001_*) as these are guaranteed to be
+ # # run on initial setup where there is no data.
+ # git diff --name-status origin/master..HEAD | grep "A\sposthog/migrations/" | awk '{print $2}' | grep -v migrations/0001_ | python manage.py test_migrations_are_safe
+ #
+ # - name: Check CH migrations
+ # run: |
+ # # Same as above, except now for CH looking at files that were added in posthog/clickhouse/migrations/
+ # git diff --name-status origin/master..HEAD | grep "A\sposthog/clickhouse/migrations/" | awk '{print $2}' | python manage.py test_ch_migrations_are_safe
django:
needs: changes
@@ -231,7 +232,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- python-version: ['3.10.10']
+ python-version: ['3.11.9']
clickhouse-server-image: ['clickhouse/clickhouse-server:23.12.5.81-alpine']
segment: ['Core']
person-on-events: [false, true]
@@ -242,7 +243,7 @@ jobs:
- segment: 'Temporal'
person-on-events: false
clickhouse-server-image: 'clickhouse/clickhouse-server:23.12.5.81-alpine'
- python-version: '3.10.10'
+ python-version: '3.11.9'
concurrency: 1
group: 1
@@ -330,7 +331,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
- python-version: 3.10.10
+ python-version: 3.11.9
cache: 'pip'
cache-dependency-path: '**/requirements*.txt'
token: ${{ secrets.POSTHOG_BOT_GITHUB_TOKEN }}
diff --git a/.github/workflows/ci-hog.yml b/.github/workflows/ci-hog.yml
index 860f0b6e47b..2a2ee8ecb86 100644
--- a/.github/workflows/ci-hog.yml
+++ b/.github/workflows/ci-hog.yml
@@ -70,7 +70,7 @@ jobs:
if: needs.changes.outputs.hog == 'true'
uses: actions/setup-python@v5
with:
- python-version: 3.10.10
+ python-version: 3.11.9
cache: 'pip'
cache-dependency-path: '**/requirements*.txt'
token: ${{ secrets.POSTHOG_BOT_GITHUB_TOKEN }}
diff --git a/.github/workflows/ci-plugin-server.yml b/.github/workflows/ci-plugin-server.yml
index dac67b705b6..b4d6cb0a17f 100644
--- a/.github/workflows/ci-plugin-server.yml
+++ b/.github/workflows/ci-plugin-server.yml
@@ -115,7 +115,7 @@ jobs:
if: needs.changes.outputs.plugin-server == 'true'
uses: actions/setup-python@v5
with:
- python-version: 3.10.10
+ python-version: 3.11.9
cache: 'pip'
cache-dependency-path: '**/requirements*.txt'
token: ${{ secrets.POSTHOG_BOT_GITHUB_TOKEN }}
@@ -207,7 +207,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
- python-version: 3.10.10
+ python-version: 3.11.9
cache: 'pip'
cache-dependency-path: '**/requirements*.txt'
token: ${{ secrets.POSTHOG_BOT_GITHUB_TOKEN }}
diff --git a/.run/Celery Beat.run.xml b/.run/Celery Beat.run.xml
index 86d7e07d05e..4141b201f1a 100644
--- a/.run/Celery Beat.run.xml
+++ b/.run/Celery Beat.run.xml
@@ -14,6 +14,7 @@
+
diff --git a/.run/Celery Threads.run.xml b/.run/Celery Threads.run.xml
index 161997f5476..e31a4ce14af 100644
--- a/.run/Celery Threads.run.xml
+++ b/.run/Celery Threads.run.xml
@@ -14,6 +14,7 @@
+
@@ -28,4 +29,4 @@
-
\ No newline at end of file
+
diff --git a/.run/Celery.run.xml b/.run/Celery.run.xml
index 77e377e642a..d6b8b57f08a 100644
--- a/.run/Celery.run.xml
+++ b/.run/Celery.run.xml
@@ -5,17 +5,18 @@
+
-
+
-
+
diff --git a/.run/PostHog.run.xml b/.run/PostHog.run.xml
index e1bbe3aeb92..54d34fc6443 100644
--- a/.run/PostHog.run.xml
+++ b/.run/PostHog.run.xml
@@ -1,9 +1,11 @@
+
+
@@ -14,14 +16,13 @@
-
-
+
@@ -47,4 +48,4 @@
-
+
\ No newline at end of file
diff --git a/bin/build-schema-python.sh b/bin/build-schema-python.sh
index 7937731b551..4f6cf8d6d65 100755
--- a/bin/build-schema-python.sh
+++ b/bin/build-schema-python.sh
@@ -4,7 +4,7 @@ set -e
# Generate schema.py from schema.json
datamodel-codegen \
- --class-name='SchemaRoot' --collapse-root-models --target-python-version 3.10 --disable-timestamp \
+ --class-name='SchemaRoot' --collapse-root-models --target-python-version 3.11 --disable-timestamp \
--use-one-literal-as-default --use-default --use-default-kwarg --use-subclass-enum \
--input frontend/src/queries/schema.json --input-file-type jsonschema \
--output posthog/schema.py --output-model-type pydantic_v2.BaseModel \
@@ -29,3 +29,9 @@ if [[ "$OSTYPE" == "darwin"* ]]; then
else
sed -i -e 's/Optional\[PropertyOperator\] = \("[A-Za-z_]*"\)/Optional[PropertyOperator] = PropertyOperator(\1)/g' posthog/schema.py
fi
+
+# Replace class Foo(str, Enum) with class Foo(StrEnum) for proper handling in format strings in python 3.11
+# Remove this when https://github.com/koxudaxi/datamodel-code-generator/issues/1313 is resolved
+
+sed -i -e 's/str, Enum/StrEnum/g' posthog/schema.py
+sed -i 's/from enum import Enum/from enum import Enum, StrEnum/g' posthog/schema.py
diff --git a/bin/deploy-hobby b/bin/deploy-hobby
index 768060c75f0..1f790dc7ba1 100755
--- a/bin/deploy-hobby
+++ b/bin/deploy-hobby
@@ -184,7 +184,7 @@ if ! command -v docker &> /dev/null; then
# Setup Docker
sudo apt install -y apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo -E apt-key add -
- sudo add-apt-repository -y "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable"
+ sudo add-apt-repository -y "deb [arch=amd64] https://download.docker.com/linux/ubuntu jammy stable"
sudo apt update
sudo apt-cache policy docker-ce
sudo apt install -y docker-ce
diff --git a/ee/api/test/__snapshots__/test_time_to_see_data.ambr b/ee/api/test/__snapshots__/test_time_to_see_data.ambr
index 2d93af68cee..beda2bc14bd 100644
--- a/ee/api/test/__snapshots__/test_time_to_see_data.ambr
+++ b/ee/api/test/__snapshots__/test_time_to_see_data.ambr
@@ -20,7 +20,7 @@
"first_name": "",
"last_name": "",
"email": "",
- "is_email_verified": false
+ "is_email_verified": null
}
},
"children": [
diff --git a/mypy.ini b/mypy.ini
index 414b1d25217..438b5f47ef6 100644
--- a/mypy.ini
+++ b/mypy.ini
@@ -1,5 +1,5 @@
[mypy]
-python_version = 3.10
+python_version = 3.11
plugins =
mypy_django_plugin.main,
mypy_drf_plugin.main,
diff --git a/posthog/api/comments.py b/posthog/api/comments.py
index 20961be0e3c..06443f92b2f 100644
--- a/posthog/api/comments.py
+++ b/posthog/api/comments.py
@@ -11,11 +11,13 @@ from posthog.api.forbid_destroy_model import ForbidDestroyModel
from posthog.api.routing import TeamAndOrgViewSetMixin
from posthog.api.shared import UserBasicSerializer
+from posthog.api.utils import ClassicBehaviorBooleanFieldSerializer
from posthog.models.comment import Comment
class CommentSerializer(serializers.ModelSerializer):
created_by = UserBasicSerializer(read_only=True)
+ deleted = ClassicBehaviorBooleanFieldSerializer()
class Meta:
model = Comment
diff --git a/posthog/api/feature_flag.py b/posthog/api/feature_flag.py
index 6887b85dcf5..029a3186d43 100644
--- a/posthog/api/feature_flag.py
+++ b/posthog/api/feature_flag.py
@@ -23,6 +23,7 @@ from posthog.api.routing import TeamAndOrgViewSetMixin
from posthog.api.shared import UserBasicSerializer
from posthog.api.tagged_item import TaggedItemSerializerMixin, TaggedItemViewSetMixin
from posthog.api.dashboards.dashboard import Dashboard
+from posthog.api.utils import ClassicBehaviorBooleanFieldSerializer
from posthog.auth import PersonalAPIKeyAuthentication, TemporaryTokenAuthentication
from posthog.constants import FlagRequestType
from posthog.event_usage import report_user_action
@@ -89,6 +90,9 @@ class FeatureFlagSerializer(TaggedItemSerializerMixin, serializers.HyperlinkedMo
is_simple_flag = serializers.SerializerMethodField()
rollout_percentage = serializers.SerializerMethodField()
+ ensure_experience_continuity = ClassicBehaviorBooleanFieldSerializer()
+ has_enriched_analytics = ClassicBehaviorBooleanFieldSerializer()
+
experiment_set: serializers.PrimaryKeyRelatedField = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
surveys: serializers.SerializerMethodField = serializers.SerializerMethodField()
features: serializers.SerializerMethodField = serializers.SerializerMethodField()
diff --git a/posthog/api/plugin.py b/posthog/api/plugin.py
index 47a5ab5b3bb..481b63476f1 100644
--- a/posthog/api/plugin.py
+++ b/posthog/api/plugin.py
@@ -22,6 +22,7 @@ from rest_framework.response import Response
from posthog.api.routing import TeamAndOrgViewSetMixin
from posthog.api.shared import FiltersSerializer
+from posthog.api.utils import ClassicBehaviorBooleanFieldSerializer
from posthog.models import Plugin, PluginAttachment, PluginConfig, User
from posthog.models.activity_logging.activity_log import (
ActivityPage,
@@ -586,6 +587,8 @@ class PluginConfigSerializer(serializers.ModelSerializer):
delivery_rate_24h = serializers.SerializerMethodField()
error = serializers.SerializerMethodField()
+ deleted = ClassicBehaviorBooleanFieldSerializer()
+
class Meta:
model = PluginConfig
fields = [
diff --git a/posthog/api/routing.py b/posthog/api/routing.py
index c4e67d18262..f2816f9a2b1 100644
--- a/posthog/api/routing.py
+++ b/posthog/api/routing.py
@@ -36,6 +36,32 @@ else:
class DefaultRouterPlusPlus(ExtendedDefaultRouter):
"""DefaultRouter with optional trailing slash and drf-extensions nesting."""
+ # This is an override because of changes in djangorestframework 3.15, which is required for python 3.11
+ # changes taken from and explained here: https://github.com/nautobot/nautobot/pull/5546/files#diff-81850a2ccad5814aab4f477d447f85cc0a82e9c10fd88fd72327cda51a750471R30
+ def _register(self, prefix, viewset, basename=None):
+ """
+ Override DRF's BaseRouter.register() to bypass an unnecessary restriction added in version 3.15.0.
+ (Reference: https://github.com/encode/django-rest-framework/pull/8438)
+ """
+ if basename is None:
+ basename = self.get_default_basename(viewset)
+
+ # DRF:
+ # if self.is_already_registered(basename):
+ # msg = (f'Router with basename "{basename}" is already registered. '
+ # f'Please provide a unique basename for viewset "{viewset}"')
+ # raise ImproperlyConfigured(msg)
+ #
+ # We bypass this because we have at least one use case (/api/extras/jobs/) where we are *intentionally*
+ # registering two viewsets with the same basename, but have carefully defined them so as not to conflict.
+
+ # resuming standard DRF code...
+ self.registry.append((prefix, viewset, basename))
+
+ # invalidate the urls cache
+ if hasattr(self, "_urls"):
+ del self._urls
+
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.trailing_slash = r"/?"
diff --git a/posthog/api/test/__snapshots__/test_api_docs.ambr b/posthog/api/test/__snapshots__/test_api_docs.ambr
index 8793984c350..2ded9229008 100644
--- a/posthog/api/test/__snapshots__/test_api_docs.ambr
+++ b/posthog/api/test/__snapshots__/test_api_docs.ambr
@@ -77,7 +77,7 @@
"/home/runner/work/posthog/posthog/posthog/api/property_definition.py: Error [PropertyDefinitionViewSet]: exception raised while getting serializer. Hint: Is get_serializer_class() returning None or is get_queryset() not working without a request? Ignoring the view for now. (Exception: 'AnonymousUser' object has no attribute 'organization')",
'/home/runner/work/posthog/posthog/posthog/api/property_definition.py: Warning [PropertyDefinitionViewSet]: could not derive type of path parameter "project_id" because model "posthog.models.property_definition.PropertyDefinition" contained no such field. Consider annotating parameter with @extend_schema. Defaulting to "string".',
'/home/runner/work/posthog/posthog/posthog/api/query.py: Warning [QueryViewSet]: could not derive type of path parameter "project_id" because it is untyped and obtaining queryset from the viewset failed. Consider adding a type to the path (e.g. ) or annotating the parameter type with @extend_schema. Defaulting to "string".',
- '/opt/hostedtoolcache/Python/3.10.10/x64/lib/python3.10/site-packages/pydantic/_internal/_model_construction.py: Warning [QueryViewSet > ModelMetaclass]: Encountered 2 components with identical names "Person" and different classes and . This will very likely result in an incorrect schema. Try renaming one.',
+ '/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/pydantic/_internal/_model_construction.py: Warning [QueryViewSet > ModelMetaclass]: Encountered 2 components with identical names "Person" and different classes and . This will very likely result in an incorrect schema. Try renaming one.',
'/home/runner/work/posthog/posthog/posthog/api/query.py: Warning [QueryViewSet]: could not derive type of path parameter "id" because it is untyped and obtaining queryset from the viewset failed. Consider adding a type to the path (e.g. ) or annotating the parameter type with @extend_schema. Defaulting to "string".',
'/home/runner/work/posthog/posthog/posthog/api/query.py: Error [QueryViewSet]: unable to guess serializer. This is graceful fallback handling for APIViews. Consider using GenericAPIView as view base class, if view is under your control. Either way you may want to add a serializer_class (or method). Ignoring view for now.',
'/home/runner/work/posthog/posthog/ee/session_recordings/session_recording_playlist.py: Warning [SessionRecordingPlaylistViewSet]: could not derive type of path parameter "project_id" because model "posthog.session_recordings.models.session_recording_playlist.SessionRecordingPlaylist" contained no such field. Consider annotating parameter with @extend_schema. Defaulting to "string".',
diff --git a/posthog/api/user.py b/posthog/api/user.py
index 8fad7945e77..ee2b66c47eb 100644
--- a/posthog/api/user.py
+++ b/posthog/api/user.py
@@ -35,7 +35,11 @@ from posthog.api.decide import hostname_in_allowed_url_list
from posthog.api.email_verification import EmailVerifier
from posthog.api.organization import OrganizationSerializer
from posthog.api.shared import OrganizationBasicSerializer, TeamBasicSerializer
-from posthog.api.utils import PublicIPOnlyHttpAdapter, raise_if_user_provided_url_unsafe
+from posthog.api.utils import (
+ PublicIPOnlyHttpAdapter,
+ raise_if_user_provided_url_unsafe,
+ ClassicBehaviorBooleanFieldSerializer,
+)
from posthog.auth import (
PersonalAPIKeyAuthentication,
SessionAuthentication,
@@ -84,6 +88,7 @@ class UserSerializer(serializers.ModelSerializer):
current_password = serializers.CharField(write_only=True, required=False)
notification_settings = serializers.DictField(required=False)
scene_personalisation = ScenePersonalisationBasicSerializer(many=True, read_only=True)
+ anonymize_data = ClassicBehaviorBooleanFieldSerializer()
class Meta:
model = User
diff --git a/posthog/api/utils.py b/posthog/api/utils.py
index 952afb9e39f..2f1bd5c087b 100644
--- a/posthog/api/utils.py
+++ b/posthog/api/utils.py
@@ -6,6 +6,8 @@ from enum import Enum, auto
from ipaddress import ip_address
from requests.adapters import HTTPAdapter
from typing import Literal, Optional, Union
+
+from rest_framework.fields import Field
from urllib3 import HTTPSConnectionPool, HTTPConnectionPool, PoolManager
from uuid import UUID
@@ -13,7 +15,7 @@ import structlog
from django.core.exceptions import RequestDataTooBig
from django.db.models import QuerySet
from prometheus_client import Counter
-from rest_framework import request, status
+from rest_framework import request, status, serializers
from rest_framework.exceptions import ValidationError
from statshog.defaults.django import statsd
@@ -34,6 +36,14 @@ class PaginationMode(Enum):
previous = auto()
+# This overrides a change in DRF 3.15 that alters our behavior. If the user passes an empty argument,
+# the new version keeps it as null vs coalescing it to the default.
+# Don't add this to new classes
+class ClassicBehaviorBooleanFieldSerializer(serializers.BooleanField):
+ def __init__(self, **kwargs):
+ Field.__init__(self, allow_null=True, required=False, **kwargs)
+
+
def get_target_entity(filter: Union[Filter, StickinessFilter]) -> Entity:
# Except for "events", we require an entity id and type to be provided
if not filter.target_entity_id and filter.target_entity_type != "events":
diff --git a/posthog/batch_exports/models.py b/posthog/batch_exports/models.py
index 598b6cdbace..7c1b3b7b0a4 100644
--- a/posthog/batch_exports/models.py
+++ b/posthog/batch_exports/models.py
@@ -254,7 +254,7 @@ class BatchExport(UUIDModel):
raise ValueError(f"Invalid interval: '{self.interval}'")
-class BatchExportLogEntryLevel(str, enum.Enum):
+class BatchExportLogEntryLevel(enum.StrEnum):
"""Enumeration of batch export log levels."""
DEBUG = "DEBUG"
diff --git a/posthog/clickhouse/table_engines.py b/posthog/clickhouse/table_engines.py
index e2b83d3f290..b67ef9be5bc 100644
--- a/posthog/clickhouse/table_engines.py
+++ b/posthog/clickhouse/table_engines.py
@@ -1,11 +1,11 @@
import uuid
-from enum import Enum
+from enum import StrEnum
from typing import Optional
from django.conf import settings
-class ReplicationScheme(str, Enum):
+class ReplicationScheme(StrEnum):
NOT_SHARDED = "NOT_SHARDED"
SHARDED = "SHARDED"
REPLICATED = "REPLICATED"
diff --git a/posthog/constants.py b/posthog/constants.py
index fc8f7a91421..af1e627bc71 100644
--- a/posthog/constants.py
+++ b/posthog/constants.py
@@ -1,4 +1,4 @@
-from enum import Enum
+from enum import StrEnum
from typing import Literal
from semantic_version import Version
@@ -9,7 +9,7 @@ INTERNAL_BOT_EMAIL_SUFFIX = "@posthogbot.user"
# N.B. Keep this in sync with frontend enum (types.ts)
# AND ensure it is added to the Billing Service
-class AvailableFeature(str, Enum):
+class AvailableFeature(StrEnum):
ZAPIER = "zapier"
ORGANIZATIONS_PROJECTS = "organizations_projects"
PROJECT_BASED_PERMISSIONING = "project_based_permissioning"
@@ -215,19 +215,19 @@ SAMPLING_FACTOR = "sampling_factor"
BREAKDOWN_TYPES = Literal["event", "person", "cohort", "group", "session", "hogql"]
-class FunnelOrderType(str, Enum):
+class FunnelOrderType(StrEnum):
STRICT = "strict"
UNORDERED = "unordered"
ORDERED = "ordered"
-class FunnelVizType(str, Enum):
+class FunnelVizType(StrEnum):
TRENDS = "trends"
TIME_TO_CONVERT = "time_to_convert"
STEPS = "steps"
-class FunnelCorrelationType(str, Enum):
+class FunnelCorrelationType(StrEnum):
EVENTS = "events"
PROPERTIES = "properties"
EVENT_WITH_PROPERTIES = "event_with_properties"
@@ -240,7 +240,7 @@ DISTINCT_ID_FILTER = "distinct_id"
PERSON_UUID_FILTER = "person_uuid"
-class AnalyticsDBMS(str, Enum):
+class AnalyticsDBMS(StrEnum):
POSTGRES = "postgres"
CLICKHOUSE = "clickhouse"
@@ -251,13 +251,13 @@ WEEKLY_ACTIVE = "weekly_active"
MONTHLY_ACTIVE = "monthly_active"
-class RetentionQueryType(str, Enum):
+class RetentionQueryType(StrEnum):
RETURNING = "returning"
TARGET = "target"
TARGET_FIRST_TIME = "target_first_time"
-class ExperimentSignificanceCode(str, Enum):
+class ExperimentSignificanceCode(StrEnum):
SIGNIFICANT = "significant"
NOT_ENOUGH_EXPOSURE = "not_enough_exposure"
LOW_WIN_PROBABILITY = "low_win_probability"
@@ -265,7 +265,7 @@ class ExperimentSignificanceCode(str, Enum):
HIGH_P_VALUE = "high_p_value"
-class ExperimentNoResultsErrorKeys(str, Enum):
+class ExperimentNoResultsErrorKeys(StrEnum):
NO_EVENTS = "no-events"
NO_FLAG_INFO = "no-flag-info"
NO_CONTROL_VARIANT = "no-control-variant"
@@ -273,12 +273,12 @@ class ExperimentNoResultsErrorKeys(str, Enum):
NO_RESULTS = "no-results"
-class PropertyOperatorType(str, Enum):
+class PropertyOperatorType(StrEnum):
AND = "AND"
OR = "OR"
-class BreakdownAttributionType(str, Enum):
+class BreakdownAttributionType(StrEnum):
FIRST_TOUCH = "first_touch"
# FIRST_TOUCH attribution means the breakdown value is the first property value found within all funnel steps
LAST_TOUCH = "last_touch"
@@ -294,7 +294,7 @@ MAX_SLUG_LENGTH = 48
GROUP_TYPES_LIMIT = 5
-class EventDefinitionType(str, Enum):
+class EventDefinitionType(StrEnum):
# Mimics EventDefinitionType in frontend/src/types.ts
ALL = "all"
ACTION_EVENT = "action_event"
@@ -303,7 +303,7 @@ class EventDefinitionType(str, Enum):
EVENT_CUSTOM = "event_custom"
-class FlagRequestType(str, Enum):
+class FlagRequestType(StrEnum):
DECIDE = "decide"
LOCAL_EVALUATION = "local-evaluation"
diff --git a/posthog/decorators.py b/posthog/decorators.py
index eb66afcf422..c4aba39e3d2 100644
--- a/posthog/decorators.py
+++ b/posthog/decorators.py
@@ -1,4 +1,4 @@
-from enum import Enum
+from enum import StrEnum
from functools import wraps
from typing import Any, TypeVar, Union, cast
from collections.abc import Callable
@@ -17,7 +17,7 @@ from posthog.utils import refresh_requested_by_client
from .utils import generate_cache_key, get_safe_cache
-class CacheType(str, Enum):
+class CacheType(StrEnum):
TRENDS = "Trends"
FUNNEL = "Funnel"
RETENTION = "Retention"
diff --git a/posthog/demo/matrix/randomization.py b/posthog/demo/matrix/randomization.py
index d017c295321..71701d2c6ce 100644
--- a/posthog/demo/matrix/randomization.py
+++ b/posthog/demo/matrix/randomization.py
@@ -1,12 +1,11 @@
-from enum import Enum
+from enum import StrEnum
-import mimesis
import mimesis.random
WeightedPool = tuple[list[str], list[int]]
-class Industry(str, Enum):
+class Industry(StrEnum):
TECHNOLOGY = "technology"
FINANCE = "finance"
MEDIA = "media"
diff --git a/posthog/demo/products/hedgebox/models.py b/posthog/demo/products/hedgebox/models.py
index dd694f64aac..9b0c72afc69 100644
--- a/posthog/demo/products/hedgebox/models.py
+++ b/posthog/demo/products/hedgebox/models.py
@@ -1,7 +1,7 @@
import datetime as dt
import math
from dataclasses import dataclass, field
-from enum import Enum, auto
+from enum import auto, StrEnum
from typing import (
TYPE_CHECKING,
Any,
@@ -66,7 +66,7 @@ class HedgeboxSessionIntent(SimSessionIntent):
DOWNGRADE_PLAN = auto()
-class HedgeboxPlan(str, Enum):
+class HedgeboxPlan(StrEnum):
PERSONAL_FREE = "personal/free"
PERSONAL_PRO = "personal/pro"
BUSINESS_STANDARD = "business/standard"
diff --git a/posthog/hogql/ast.py b/posthog/hogql/ast.py
index 976d245981b..72b2c32f7b7 100644
--- a/posthog/hogql/ast.py
+++ b/posthog/hogql/ast.py
@@ -1,4 +1,4 @@
-from enum import Enum
+from enum import StrEnum
from typing import Any, Literal, Optional, Union
from dataclasses import dataclass, field
@@ -394,7 +394,7 @@ class UUIDType(ConstantType):
@dataclass(kw_only=True)
class ArrayType(ConstantType):
data_type: ConstantDataType = field(default="array", init=False)
- item_type: ConstantType = UnknownType()
+ item_type: ConstantType = field(default_factory=UnknownType)
def print_type(self) -> str:
return "Array"
@@ -554,7 +554,7 @@ class Alias(Expr):
hidden: bool = False
-class ArithmeticOperationOp(str, Enum):
+class ArithmeticOperationOp(StrEnum):
Add = "+"
Sub = "-"
Mult = "*"
@@ -581,7 +581,7 @@ class Or(Expr):
type: Optional[ConstantType] = None
-class CompareOperationOp(str, Enum):
+class CompareOperationOp(StrEnum):
Eq = "=="
NotEq = "!="
Gt = ">"
diff --git a/posthog/hogql/constants.py b/posthog/hogql/constants.py
index 769d4a250e6..f484a6d0fad 100644
--- a/posthog/hogql/constants.py
+++ b/posthog/hogql/constants.py
@@ -1,5 +1,5 @@
from datetime import date, datetime
-from enum import Enum
+from enum import StrEnum
from typing import Optional, Literal, TypeAlias
from uuid import UUID
from pydantic import ConfigDict, BaseModel
@@ -47,7 +47,7 @@ BREAKDOWN_VALUES_LIMIT = 25
BREAKDOWN_VALUES_LIMIT_FOR_COUNTRIES = 300
-class LimitContext(str, Enum):
+class LimitContext(StrEnum):
QUERY = "query"
QUERY_ASYNC = "query_async"
EXPORT = "export"
diff --git a/posthog/hogql_queries/legacy_compatibility/filter_to_query.py b/posthog/hogql_queries/legacy_compatibility/filter_to_query.py
index 996e1017381..b06b4bb415a 100644
--- a/posthog/hogql_queries/legacy_compatibility/filter_to_query.py
+++ b/posthog/hogql_queries/legacy_compatibility/filter_to_query.py
@@ -1,5 +1,5 @@
import copy
-from enum import Enum
+from enum import StrEnum
import json
from typing import Any, Literal
from posthog.hogql_queries.legacy_compatibility.clean_properties import clean_entity_properties, clean_global_properties
@@ -34,7 +34,7 @@ from posthog.types import InsightQueryNode
from posthog.utils import str_to_bool
-class MathAvailability(str, Enum):
+class MathAvailability(StrEnum):
Unavailable = ("Unavailable",)
All = ("All",)
ActorsOnly = "ActorsOnly"
diff --git a/posthog/kafka_client/client.py b/posthog/kafka_client/client.py
index 3f58e572417..f0008c4ba72 100644
--- a/posthog/kafka_client/client.py
+++ b/posthog/kafka_client/client.py
@@ -1,5 +1,5 @@
import json
-from enum import Enum
+from enum import StrEnum
from typing import Any, Optional
from collections.abc import Callable
@@ -83,7 +83,7 @@ class KafkaConsumerForTests:
return
-class _KafkaSecurityProtocol(str, Enum):
+class _KafkaSecurityProtocol(StrEnum):
PLAINTEXT = "PLAINTEXT"
SSL = "SSL"
SASL_PLAINTEXT = "SASL_PLAINTEXT"
diff --git a/posthog/management/commands/create_channel_definitions_file.py b/posthog/management/commands/create_channel_definitions_file.py
index cab70bf31d3..bea98c02b52 100644
--- a/posthog/management/commands/create_channel_definitions_file.py
+++ b/posthog/management/commands/create_channel_definitions_file.py
@@ -3,7 +3,7 @@ import re
import subprocess
from collections import OrderedDict
from dataclasses import dataclass
-from enum import Enum
+from enum import StrEnum
from typing import Optional
from django.core.management.base import BaseCommand
@@ -12,7 +12,7 @@ from django.core.management.base import BaseCommand
OUTPUT_FILE = "posthog/models/channel_type/channel_definitions.json"
-class EntryKind(str, Enum):
+class EntryKind(StrEnum):
source = "source"
medium = "medium"
diff --git a/posthog/models/feature_flag/flag_matching.py b/posthog/models/feature_flag/flag_matching.py
index 70e0190a570..ea181081f0c 100644
--- a/posthog/models/feature_flag/flag_matching.py
+++ b/posthog/models/feature_flag/flag_matching.py
@@ -1,6 +1,6 @@
import hashlib
from dataclasses import dataclass
-from enum import Enum
+from enum import StrEnum
import time
import structlog
from typing import Literal, Optional, Union, cast
@@ -67,7 +67,7 @@ ENTITY_EXISTS_PREFIX = "flag_entity_exists_"
PERSON_KEY = "person"
-class FeatureFlagMatchReason(str, Enum):
+class FeatureFlagMatchReason(StrEnum):
SUPER_CONDITION_VALUE = "super_condition_value"
CONDITION_MATCH = "condition_match"
NO_CONDITION_MATCH = "no_condition_match"
diff --git a/posthog/models/plugin.py b/posthog/models/plugin.py
index 46ddfb9177f..87ab0497c81 100644
--- a/posthog/models/plugin.py
+++ b/posthog/models/plugin.py
@@ -1,7 +1,7 @@
import datetime
import os
from dataclasses import dataclass
-from enum import Enum
+from enum import StrEnum
from typing import Any, Optional, cast
from uuid import UUID
@@ -288,13 +288,13 @@ class PluginStorage(models.Model):
value: models.TextField = models.TextField(blank=True, null=True)
-class PluginLogEntrySource(str, Enum):
+class PluginLogEntrySource(StrEnum):
SYSTEM = "SYSTEM"
PLUGIN = "PLUGIN"
CONSOLE = "CONSOLE"
-class PluginLogEntryType(str, Enum):
+class PluginLogEntryType(StrEnum):
DEBUG = "DEBUG"
LOG = "LOG"
INFO = "INFO"
diff --git a/posthog/models/property/property.py b/posthog/models/property/property.py
index 7185306b8cc..bb378b7616d 100644
--- a/posthog/models/property/property.py
+++ b/posthog/models/property/property.py
@@ -1,5 +1,5 @@
import json
-from enum import Enum
+from enum import StrEnum
from typing import (
Any,
Literal,
@@ -14,7 +14,7 @@ from posthog.models.filters.utils import GroupTypeIndex, validate_group_type_ind
from posthog.utils import str_to_bool
-class BehavioralPropertyType(str, Enum):
+class BehavioralPropertyType(StrEnum):
PERFORMED_EVENT = "performed_event"
PERFORMED_EVENT_MULTIPLE = "performed_event_multiple"
PERFORMED_EVENT_FIRST_TIME = "performed_event_first_time"
diff --git a/posthog/schema.py b/posthog/schema.py
index 28df175d30a..eecd761ec0c 100644
--- a/posthog/schema.py
+++ b/posthog/schema.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from enum import Enum
+from enum import Enum, StrEnum
from typing import Any, Literal, Optional, Union
from pydantic import AwareDatetime, BaseModel, ConfigDict, Field, RootModel
@@ -20,7 +20,7 @@ class MathGroupTypeIndex(float, Enum):
NUMBER_4 = 4
-class AggregationAxisFormat(str, Enum):
+class AggregationAxisFormat(StrEnum):
NUMERIC = "numeric"
DURATION = "duration"
DURATION_MS = "duration_ms"
@@ -28,7 +28,7 @@ class AggregationAxisFormat(str, Enum):
PERCENTAGE_SCALED = "percentage_scaled"
-class Kind(str, Enum):
+class Kind(StrEnum):
METHOD = "Method"
FUNCTION = "Function"
CONSTRUCTOR = "Constructor"
@@ -87,7 +87,7 @@ class AutocompleteCompletionItem(BaseModel):
)
-class BaseMathType(str, Enum):
+class BaseMathType(StrEnum):
TOTAL = "total"
DAU = "dau"
WEEKLY_ACTIVE = "weekly_active"
@@ -95,14 +95,14 @@ class BaseMathType(str, Enum):
UNIQUE_SESSION = "unique_session"
-class BreakdownAttributionType(str, Enum):
+class BreakdownAttributionType(StrEnum):
FIRST_TOUCH = "first_touch"
LAST_TOUCH = "last_touch"
ALL_EVENTS = "all_events"
STEP = "step"
-class BreakdownType(str, Enum):
+class BreakdownType(StrEnum):
COHORT = "cohort"
PERSON = "person"
EVENT = "event"
@@ -164,7 +164,7 @@ class ChartAxis(BaseModel):
column: str
-class ChartDisplayType(str, Enum):
+class ChartDisplayType(StrEnum):
ACTIONS_LINE_GRAPH = "ActionsLineGraph"
ACTIONS_BAR = "ActionsBar"
ACTIONS_AREA_GRAPH = "ActionsAreaGraph"
@@ -205,7 +205,7 @@ class CompareFilter(BaseModel):
compare_to: Optional[str] = None
-class CountPerActorMathType(str, Enum):
+class CountPerActorMathType(StrEnum):
AVG_COUNT_PER_ACTOR = "avg_count_per_actor"
MIN_COUNT_PER_ACTOR = "min_count_per_actor"
MAX_COUNT_PER_ACTOR = "max_count_per_actor"
@@ -255,14 +255,14 @@ class DatabaseSchemaSource(BaseModel):
status: str
-class Type(str, Enum):
+class Type(StrEnum):
POSTHOG = "posthog"
DATA_WAREHOUSE = "data_warehouse"
VIEW = "view"
BATCH_EXPORT = "batch_export"
-class DatabaseSerializedFieldType(str, Enum):
+class DatabaseSerializedFieldType(StrEnum):
INTEGER = "integer"
FLOAT = "float"
STRING = "string"
@@ -301,13 +301,13 @@ class Day(RootModel[int]):
root: int
-class DurationType(str, Enum):
+class DurationType(StrEnum):
DURATION = "duration"
ACTIVE_SECONDS = "active_seconds"
INACTIVE_SECONDS = "inactive_seconds"
-class Key(str, Enum):
+class Key(StrEnum):
TAG_NAME = "tag_name"
TEXT = "text"
HREF = "href"
@@ -336,14 +336,14 @@ class EmptyPropertyFilter(BaseModel):
)
-class EntityType(str, Enum):
+class EntityType(StrEnum):
ACTIONS = "actions"
EVENTS = "events"
DATA_WAREHOUSE = "data_warehouse"
NEW_ENTITY = "new_entity"
-class ErrorTrackingOrder(str, Enum):
+class ErrorTrackingOrder(StrEnum):
LAST_SEEN = "last_seen"
FIRST_SEEN = "first_seen"
UNIQUE_OCCURRENCES = "unique_occurrences"
@@ -360,7 +360,7 @@ class EventDefinition(BaseModel):
properties: dict[str, Any]
-class CorrelationType(str, Enum):
+class CorrelationType(StrEnum):
SUCCESS = "success"
FAILURE = "failure"
@@ -418,12 +418,12 @@ class EventsQueryPersonColumn(BaseModel):
uuid: str
-class FilterLogicalOperator(str, Enum):
+class FilterLogicalOperator(StrEnum):
AND_ = "AND"
OR_ = "OR"
-class FunnelConversionWindowTimeUnit(str, Enum):
+class FunnelConversionWindowTimeUnit(StrEnum):
SECOND = "second"
MINUTE = "minute"
HOUR = "hour"
@@ -440,7 +440,7 @@ class FunnelCorrelationResult(BaseModel):
skewed: bool
-class FunnelCorrelationResultsType(str, Enum):
+class FunnelCorrelationResultsType(StrEnum):
EVENTS = "events"
PROPERTIES = "properties"
EVENT_WITH_PROPERTIES = "event_with_properties"
@@ -468,18 +468,18 @@ class FunnelExclusionSteps(BaseModel):
funnelToStep: int
-class FunnelLayout(str, Enum):
+class FunnelLayout(StrEnum):
HORIZONTAL = "horizontal"
VERTICAL = "vertical"
-class FunnelPathType(str, Enum):
+class FunnelPathType(StrEnum):
FUNNEL_PATH_BEFORE_STEP = "funnel_path_before_step"
FUNNEL_PATH_BETWEEN_STEPS = "funnel_path_between_steps"
FUNNEL_PATH_AFTER_STEP = "funnel_path_after_step"
-class FunnelStepReference(str, Enum):
+class FunnelStepReference(StrEnum):
TOTAL = "total"
PREVIOUS = "previous"
@@ -492,7 +492,7 @@ class FunnelTimeToConvertResults(BaseModel):
bins: list[list[int]]
-class FunnelVizType(str, Enum):
+class FunnelVizType(StrEnum):
STEPS = "steps"
TIME_TO_CONVERT = "time_to_convert"
TRENDS = "trends"
@@ -516,37 +516,37 @@ class HogQLNotice(BaseModel):
start: Optional[int] = None
-class BounceRatePageViewMode(str, Enum):
+class BounceRatePageViewMode(StrEnum):
COUNT_PAGEVIEWS = "count_pageviews"
UNIQ_URLS = "uniq_urls"
-class InCohortVia(str, Enum):
+class InCohortVia(StrEnum):
AUTO = "auto"
LEFTJOIN = "leftjoin"
SUBQUERY = "subquery"
LEFTJOIN_CONJOINED = "leftjoin_conjoined"
-class MaterializationMode(str, Enum):
+class MaterializationMode(StrEnum):
AUTO = "auto"
LEGACY_NULL_AS_STRING = "legacy_null_as_string"
LEGACY_NULL_AS_NULL = "legacy_null_as_null"
DISABLED = "disabled"
-class PersonsArgMaxVersion(str, Enum):
+class PersonsArgMaxVersion(StrEnum):
AUTO = "auto"
V1 = "v1"
V2 = "v2"
-class PersonsJoinMode(str, Enum):
+class PersonsJoinMode(StrEnum):
INNER = "inner"
LEFT = "left"
-class PersonsOnEventsMode(str, Enum):
+class PersonsOnEventsMode(StrEnum):
DISABLED = "disabled"
PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS = "person_id_no_override_properties_on_events"
PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS = "person_id_override_properties_on_events"
@@ -579,7 +579,7 @@ class HogQueryResponse(BaseModel):
stdout: Optional[str] = None
-class Compare(str, Enum):
+class Compare(StrEnum):
CURRENT = "current"
PREVIOUS = "previous"
@@ -619,7 +619,7 @@ class InsightDateRange(BaseModel):
)
-class InsightFilterProperty(str, Enum):
+class InsightFilterProperty(StrEnum):
TRENDS_FILTER = "trendsFilter"
FUNNELS_FILTER = "funnelsFilter"
RETENTION_FILTER = "retentionFilter"
@@ -628,7 +628,7 @@ class InsightFilterProperty(str, Enum):
LIFECYCLE_FILTER = "lifecycleFilter"
-class InsightNodeKind(str, Enum):
+class InsightNodeKind(StrEnum):
TRENDS_QUERY = "TrendsQuery"
FUNNELS_QUERY = "FunnelsQuery"
RETENTION_QUERY = "RetentionQuery"
@@ -637,7 +637,7 @@ class InsightNodeKind(str, Enum):
LIFECYCLE_QUERY = "LifecycleQuery"
-class InsightType(str, Enum):
+class InsightType(StrEnum):
TRENDS = "TRENDS"
STICKINESS = "STICKINESS"
LIFECYCLE = "LIFECYCLE"
@@ -649,7 +649,7 @@ class InsightType(str, Enum):
HOG = "HOG"
-class IntervalType(str, Enum):
+class IntervalType(StrEnum):
MINUTE = "minute"
HOUR = "hour"
DAY = "day"
@@ -657,14 +657,14 @@ class IntervalType(str, Enum):
MONTH = "month"
-class LifecycleToggle(str, Enum):
+class LifecycleToggle(StrEnum):
NEW = "new"
RESURRECTING = "resurrecting"
RETURNING = "returning"
DORMANT = "dormant"
-class NodeKind(str, Enum):
+class NodeKind(StrEnum):
EVENTS_NODE = "EventsNode"
ACTIONS_NODE = "ActionsNode"
DATA_WAREHOUSE_NODE = "DataWarehouseNode"
@@ -709,7 +709,7 @@ class PathCleaningFilter(BaseModel):
regex: Optional[str] = None
-class PathType(str, Enum):
+class PathType(StrEnum):
FIELD_PAGEVIEW = "$pageview"
FIELD_SCREEN = "$screen"
CUSTOM_EVENT = "custom_event"
@@ -758,7 +758,7 @@ class PathsFilterLegacy(BaseModel):
step_limit: Optional[int] = None
-class PropertyFilterType(str, Enum):
+class PropertyFilterType(StrEnum):
META = "meta"
EVENT = "event"
PERSON = "person"
@@ -773,7 +773,7 @@ class PropertyFilterType(str, Enum):
DATA_WAREHOUSE_PERSON_PROPERTY = "data_warehouse_person_property"
-class PropertyMathType(str, Enum):
+class PropertyMathType(StrEnum):
AVG = "avg"
SUM = "sum"
MIN = "min"
@@ -784,7 +784,7 @@ class PropertyMathType(str, Enum):
P99 = "p99"
-class PropertyOperator(str, Enum):
+class PropertyOperator(StrEnum):
EXACT = "exact"
IS_NOT = "is_not"
ICONTAINS = "icontains"
@@ -899,7 +899,7 @@ class RecordingPropertyFilter(BaseModel):
value: Optional[Union[str, float, list[Union[str, float]]]] = None
-class Kind1(str, Enum):
+class Kind1(StrEnum):
ACTIONS_NODE = "ActionsNode"
EVENTS_NODE = "EventsNode"
@@ -917,19 +917,19 @@ class RetentionEntity(BaseModel):
uuid: Optional[str] = None
-class RetentionReference(str, Enum):
+class RetentionReference(StrEnum):
TOTAL = "total"
PREVIOUS = "previous"
-class RetentionPeriod(str, Enum):
+class RetentionPeriod(StrEnum):
HOUR = "Hour"
DAY = "Day"
WEEK = "Week"
MONTH = "Month"
-class RetentionType(str, Enum):
+class RetentionType(StrEnum):
RETENTION_RECURRING = "retention_recurring"
RETENTION_FIRST_TIME = "retention_first_time"
@@ -960,7 +960,7 @@ class SessionPropertyFilter(BaseModel):
value: Optional[Union[str, float, list[Union[str, float]]]] = None
-class StepOrderValue(str, Enum):
+class StepOrderValue(StrEnum):
STRICT = "strict"
UNORDERED = "unordered"
ORDERED = "ordered"
@@ -1175,7 +1175,7 @@ class VizSpecificOptions(BaseModel):
RETENTION: Optional[RETENTION] = None
-class Kind2(str, Enum):
+class Kind2(StrEnum):
UNIT = "unit"
DURATION_S = "duration_s"
PERCENTAGE = "percentage"
@@ -1222,7 +1222,7 @@ class WebOverviewQueryResponse(BaseModel):
)
-class WebStatsBreakdown(str, Enum):
+class WebStatsBreakdown(StrEnum):
PAGE = "Page"
INITIAL_PAGE = "InitialPage"
EXIT_PAGE = "ExitPage"
diff --git a/production.Dockerfile b/production.Dockerfile
index 1e3eb2d1155..30fa7146a77 100644
--- a/production.Dockerfile
+++ b/production.Dockerfile
@@ -83,7 +83,7 @@ RUN corepack enable && \
#
# ---------------------------------------------------------
#
-FROM python:3.10.10-slim-bullseye AS posthog-build
+FROM python:3.11.9-slim-bookworm AS posthog-build
WORKDIR /code
SHELL ["/bin/bash", "-e", "-o", "pipefail", "-c"]
@@ -139,104 +139,7 @@ RUN apt-get update && \
#
# ---------------------------------------------------------
#
-# Build a version of the unit docker image for python3.10
-# We can remove this step once we are on python3.11
-FROM unit:python3.11 as unit
-FROM python:3.10-bullseye as unit-131-python-310
-
-# copied from https://github.com/nginx/unit/blob/master/pkg/docker/Dockerfile.python3.11
-LABEL org.opencontainers.image.title="Unit (python3.10)"
-LABEL org.opencontainers.image.description="Official build of Unit for Docker."
-LABEL org.opencontainers.image.url="https://unit.nginx.org"
-LABEL org.opencontainers.image.source="https://github.com/nginx/unit"
-LABEL org.opencontainers.image.documentation="https://unit.nginx.org/installation/#docker-images"
-LABEL org.opencontainers.image.vendor="NGINX Docker Maintainers "
-LABEL org.opencontainers.image.version="1.31.1"
-
-RUN set -ex \
- && savedAptMark="$(apt-mark showmanual)" \
- && apt-get update \
- && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates mercurial build-essential libssl-dev libpcre2-dev curl pkg-config \
- && mkdir -p /usr/lib/unit/modules /usr/lib/unit/debug-modules \
- && mkdir -p /usr/src/unit \
- && cd /usr/src/unit \
- && hg clone -u 1.31.1-1 https://hg.nginx.org/unit \
- && cd unit \
- && NCPU="$(getconf _NPROCESSORS_ONLN)" \
- && DEB_HOST_MULTIARCH="$(dpkg-architecture -q DEB_HOST_MULTIARCH)" \
- && CC_OPT="$(DEB_BUILD_MAINT_OPTIONS="hardening=+all,-pie" DEB_CFLAGS_MAINT_APPEND="-Wp,-D_FORTIFY_SOURCE=2 -fPIC" dpkg-buildflags --get CFLAGS)" \
- && LD_OPT="$(DEB_BUILD_MAINT_OPTIONS="hardening=+all,-pie" DEB_LDFLAGS_MAINT_APPEND="-Wl,--as-needed -pie" dpkg-buildflags --get LDFLAGS)" \
- && CONFIGURE_ARGS_MODULES="--prefix=/usr \
- --statedir=/var/lib/unit \
- --control=unix:/var/run/control.unit.sock \
- --runstatedir=/var/run \
- --pid=/var/run/unit.pid \
- --logdir=/var/log \
- --log=/var/log/unit.log \
- --tmpdir=/var/tmp \
- --user=unit \
- --group=unit \
- --openssl \
- --libdir=/usr/lib/$DEB_HOST_MULTIARCH" \
- && CONFIGURE_ARGS="$CONFIGURE_ARGS_MODULES \
- --njs" \
- && make -j $NCPU -C pkg/contrib .njs \
- && export PKG_CONFIG_PATH=$(pwd)/pkg/contrib/njs/build \
- && ./configure $CONFIGURE_ARGS --cc-opt="$CC_OPT" --ld-opt="$LD_OPT" --modulesdir=/usr/lib/unit/debug-modules --debug \
- && make -j $NCPU unitd \
- && install -pm755 build/sbin/unitd /usr/sbin/unitd-debug \
- && make clean \
- && ./configure $CONFIGURE_ARGS --cc-opt="$CC_OPT" --ld-opt="$LD_OPT" --modulesdir=/usr/lib/unit/modules \
- && make -j $NCPU unitd \
- && install -pm755 build/sbin/unitd /usr/sbin/unitd \
- && make clean \
- && /bin/true \
- && ./configure $CONFIGURE_ARGS_MODULES --cc-opt="$CC_OPT" --modulesdir=/usr/lib/unit/debug-modules --debug \
- && ./configure python --config=/usr/local/bin/python3-config \
- && make -j $NCPU python3-install \
- && make clean \
- && ./configure $CONFIGURE_ARGS_MODULES --cc-opt="$CC_OPT" --modulesdir=/usr/lib/unit/modules \
- && ./configure python --config=/usr/local/bin/python3-config \
- && make -j $NCPU python3-install \
- && cd \
- && rm -rf /usr/src/unit \
- && for f in /usr/sbin/unitd /usr/lib/unit/modules/*.unit.so; do \
- ldd $f | awk '/=>/{print $(NF-1)}' | while read n; do dpkg-query -S $n; done | sed 's/^\([^:]\+\):.*$/\1/' | sort | uniq >> /requirements.apt; \
- done \
- && apt-mark showmanual | xargs apt-mark auto > /dev/null \
- && { [ -z "$savedAptMark" ] || apt-mark manual $savedAptMark; } \
- && /bin/true \
- && mkdir -p /var/lib/unit/ \
- && mkdir -p /docker-entrypoint.d/ \
- && groupadd --gid 998 unit \
- && useradd \
- --uid 998 \
- --gid unit \
- --no-create-home \
- --home /nonexistent \
- --comment "unit user" \
- --shell /bin/false \
- unit \
- && apt-get update \
- && apt-get --no-install-recommends --no-install-suggests -y install curl $(cat /requirements.apt) \
- && apt-get purge -y --auto-remove build-essential \
- && rm -rf /var/lib/apt/lists/* \
- && rm -f /requirements.apt \
- && ln -sf /dev/stdout /var/log/unit.log
-
-COPY --from=unit /usr/local/bin/docker-entrypoint.sh /usr/local/bin/
-COPY --from=unit /usr/share/unit/welcome/welcome.* /usr/share/unit/welcome/
-
-STOPSIGNAL SIGTERM
-
-ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
-EXPOSE 80
-CMD ["unitd", "--no-daemon", "--control", "unix:/var/run/control.unit.sock"]
-
-#
-# ---------------------------------------------------------
-#
-FROM unit-131-python-310
+FROM unit:python3.11
WORKDIR /code
SHELL ["/bin/bash", "-e", "-o", "pipefail", "-c"]
ENV PYTHONUNBUFFERED 1
@@ -265,7 +168,7 @@ RUN apt-get install -y --no-install-recommends \
# Install and use a non-root user.
RUN groupadd -g 1000 posthog && \
- useradd -u 999 -r -g posthog posthog && \
+ useradd -r -g posthog posthog && \
chown posthog:posthog /code
USER posthog
diff --git a/requirements-dev.txt b/requirements-dev.txt
index ded32c00acb..dbf468cd45b 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -1,9 +1,5 @@
-#
-# This file is autogenerated by pip-compile with Python 3.10
-# by the following command:
-#
-# pip-compile --output-file=requirements-dev.txt requirements-dev.in
-#
+# This file was autogenerated by uv via the following command:
+# uv pip compile requirements-dev.in -o requirements-dev.txt
aiohttp==3.9.3
# via
# -c requirements.txt
@@ -42,7 +38,7 @@ black==23.9.1
# -r requirements-dev.in
# datamodel-code-generator
# inline-snapshot
-boto3-stubs[s3]==1.34.84
+boto3-stubs==1.34.84
# via -r requirements-dev.in
botocore-stubs==1.34.84
# via boto3-stubs
@@ -50,7 +46,7 @@ certifi==2019.11.28
# via
# -c requirements.txt
# requests
-cffi==1.14.5
+cffi==1.16.0
# via
# -c requirements.txt
# cryptography
@@ -67,9 +63,9 @@ click==8.1.7
# inline-snapshot
colorama==0.4.4
# via pytest-watch
-coverage[toml]==5.5
+coverage==5.5
# via pytest-cov
-cryptography==37.0.2
+cryptography==39.0.2
# via
# -c requirements.txt
# types-paramiko
@@ -106,13 +102,13 @@ executing==2.0.1
# via inline-snapshot
faker==17.5.0
# via -r requirements-dev.in
-fakeredis[lua]==2.11.0
+fakeredis==2.11.0
# via -r requirements-dev.in
flaky==3.7.0
# via -r requirements-dev.in
freezegun==1.2.2
# via -r requirements-dev.in
-frozenlist==1.3.0
+frozenlist==1.4.1
# via
# -c requirements.txt
# aiohttp
@@ -207,7 +203,7 @@ pycparser==2.20
# via
# -c requirements.txt
# cffi
-pydantic[email]==2.5.3
+pydantic==2.5.3
# via
# -c requirements.txt
# datamodel-code-generator
@@ -377,7 +373,7 @@ urllib3==1.26.18
# responses
watchdog==2.1.8
# via pytest-watch
-yarl==1.7.2
+yarl==1.9.4
# via
# -c requirements.txt
# aiohttp
diff --git a/requirements.in b/requirements.in
index ab8fa919f23..6b59c0ad8f1 100644
--- a/requirements.in
+++ b/requirements.in
@@ -14,7 +14,7 @@ celery==5.3.4
celery-redbeat==2.1.1
clickhouse-driver==0.2.7
clickhouse-pool==0.5.3
-cryptography==37.0.2
+cryptography==39.0.2
dj-database-url==0.5.0
Django~=4.2.11
django-axes==5.9.0
@@ -29,7 +29,7 @@ django-redis==5.2.0
django-statsd==2.5.2
django-structlog==2.1.3
django-revproxy==0.12.0
-djangorestframework==3.14.0
+djangorestframework==3.15.1
djangorestframework-csv==2.1.1
djangorestframework-dataclasses==1.2.0
django-fernet-encrypted-fields==0.1.3
@@ -96,3 +96,5 @@ nh3==0.2.14
hogql-parser==1.0.21
zxcvbn==4.4.28
zstd==1.5.5.1
+xmlsec==1.3.14
+lxml==5.2.2
diff --git a/requirements.txt b/requirements.txt
index 636ddd713dc..13796a6133f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -84,7 +84,7 @@ certifi==2019.11.28
# sentry-sdk
# snowflake-connector-python
# urllib3
-cffi==1.14.5
+cffi==1.16.0
# via
# cryptography
# pynacl
@@ -113,7 +113,7 @@ clickhouse-driver==0.2.7
# sentry-sdk
clickhouse-pool==0.5.3
# via -r requirements.in
-cryptography==37.0.2
+cryptography==39.0.2
# via
# -r requirements.in
# django-fernet-encrypted-fields
@@ -195,7 +195,7 @@ django-structlog==2.1.3
# via -r requirements.in
django-two-factor-auth==1.14.0
# via -r requirements.in
-djangorestframework==3.14.0
+djangorestframework==3.15.1
# via
# -r requirements.in
# djangorestframework-csv
@@ -223,7 +223,7 @@ exceptiongroup==1.2.1
# via anyio
filelock==3.12.0
# via snowflake-connector-python
-frozenlist==1.3.0
+frozenlist==1.4.1
# via
# aiohttp
# aiosignal
@@ -321,8 +321,9 @@ kombu==5.3.2
# via
# -r requirements.in
# celery
-lxml==4.9.4
+lxml==5.2.2
# via
+ # -r requirements.in
# python3-saml
# toronado
# xmlsec
@@ -443,7 +444,7 @@ pyjwt==2.4.0
# social-auth-core
pynacl==1.5.0
# via paramiko
-pyopenssl==22.0.0
+pyopenssl==23.0.0
# via
# snowflake-connector-python
# urllib3
@@ -474,7 +475,6 @@ pytz==2023.3
# via
# -r requirements.in
# clickhouse-driver
- # djangorestframework
# dlt
# infi-clickhouse-orm
# pandas
@@ -623,7 +623,7 @@ toronado==0.1.0
# via -r requirements.in
tqdm==4.64.1
# via openai
-trio==0.20.0
+trio==0.21.0
# via
# selenium
# trio-websocket
@@ -685,9 +685,11 @@ wrapt==1.15.0
# via aiobotocore
wsproto==1.1.0
# via trio-websocket
-xmlsec==1.3.13
- # via python3-saml
-yarl==1.7.2
+xmlsec==1.3.14
+ # via
+ # -r requirements.in
+ # python3-saml
+yarl==1.9.4
# via aiohttp
zstd==1.5.5.1
# via -r requirements.in
diff --git a/unit.json.tpl b/unit.json.tpl
index ef1ba4b3ffe..42f23a75a03 100644
--- a/unit.json.tpl
+++ b/unit.json.tpl
@@ -39,7 +39,7 @@
},
"applications": {
"posthog": {
- "type": "python 3.10",
+ "type": "python 3.11",
"processes": $NGINX_UNIT_APP_PROCESSES,
"working_directory": "/code",
"path": ".",
@@ -51,7 +51,7 @@
}
},
"metrics": {
- "type": "python 3.10",
+ "type": "python 3.11",
"processes": 1,
"working_directory": "/code/bin",
"path": ".",