Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions backend/backend/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,9 @@ class RequestHeader:

class UrlPathConstants:
PROMPT_STUDIO = "prompt-studio/"


class FeatureFlag:
"""Temporary feature flags."""

MULTI_TENANCY_V2 = "multi_tenancy_v2"
86 changes: 70 additions & 16 deletions backend/backend/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@

from dotenv import find_dotenv, load_dotenv

from backend.constants import FeatureFlag
from unstract.flags.feature_flag import check_feature_flag_status

missing_settings = []


Expand Down Expand Up @@ -185,7 +188,6 @@ def get_required_setting(
# Application definition
SHARED_APPS = (
# Multitenancy
"django_tenants",
"corsheaders",
# For the organization model
"account",
Expand All @@ -211,6 +213,9 @@ def get_required_setting(
"django_celery_beat",
)

if not check_feature_flag_status(FeatureFlag.MULTI_TENANCY_V2):
SHARED_APPS = ("django_tenants",) + SHARED_APPS

TENANT_APPS = (
# your tenant-specific apps
"django.contrib.admin",
Expand Down Expand Up @@ -251,30 +256,84 @@ def get_required_setting(
GOOGLE_MODEL_BACKEND,
)

TENANT_MODEL = "account.Organization"
TENANT_DOMAIN_MODEL = "account.Domain"
AUTH_USER_MODEL = "account.User"
PUBLIC_ORG_ID = "public"

if check_feature_flag_status(FeatureFlag.MULTI_TENANCY_V2):
# Middleware Configuration
TENANT_MIDDLEWARE = "middleware.organization_middleware.OrganizationMiddleware"
CUSTOM_AUTH_MIDDLEWARE = "account_v2.custom_auth_middleware.CustomAuthMiddleware"

# Pipeline Functions
SOCIAL_AUTH_PIPELINE_USER_AUTH = (
"connector_auth_v2.pipeline.common.check_user_exists"
)
SOCIAL_AUTH_PIPELINE_CACHE_CRED = (
"connector_auth_v2.pipeline.common.cache_oauth_creds"
)

# Routing Configuration
ROOT_URLCONF = "backend.base_urls"

# DB Configuration
DB_ENGINE = "django.db.backends.postgresql"

# Models
AUTH_USER_MODEL = "account_v2.User"

# Social Authentication
SOCIAL_AUTH_USER_MODEL = "account_v2.User"
SOCIAL_AUTH_STORAGE = "connector_auth_v2.models.ConnectorDjangoStorage"

# Namespaces
SOCIAL_AUTH_URL_NAMESPACE = "public:social"
LOGIN_CALLBACK_URL_NAMESPACE = "public:callback"
else:
# Middleware Configuration
TENANT_MIDDLEWARE = "django_tenants.middleware.TenantSubfolderMiddleware"
CUSTOM_AUTH_MIDDLEWARE = "account.custom_auth_middleware.CustomAuthMiddleware"

# Pipeline Functions
SOCIAL_AUTH_PIPELINE_USER_AUTH = "connector_auth.pipeline.common.check_user_exists"
SOCIAL_AUTH_PIPELINE_CACHE_CRED = "connector_auth.pipeline.common.cache_oauth_creds"

# Routing Configuration
PUBLIC_SCHEMA_URLCONF = "backend.public_urls"
ROOT_URLCONF = "backend.urls"

# DB Configuration
DB_ENGINE = "django_tenants.postgresql_backend"
DATABASE_ROUTERS = ("django_tenants.routers.TenantSyncRouter",)

# Models
AUTH_USER_MODEL = "account.User"
TENANT_MODEL = "account.Organization"
TENANT_DOMAIN_MODEL = "account.Domain"

# Social Authentication
SOCIAL_AUTH_USER_MODEL = "account.User"
SOCIAL_AUTH_STORAGE = "connector_auth.models.ConnectorDjangoStorage"

# Namespaces
SOCIAL_AUTH_URL_NAMESPACE = "social"
LOGIN_CALLBACK_URL_NAMESPACE = "callback"

MIDDLEWARE = [
"log_request_id.middleware.RequestIDMiddleware",
"corsheaders.middleware.CorsMiddleware",
"django_tenants.middleware.TenantSubfolderMiddleware",
TENANT_MIDDLEWARE,
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.middleware.common.CommonMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"account.custom_auth_middleware.CustomAuthMiddleware",
CUSTOM_AUTH_MIDDLEWARE,
"middleware.exception.ExceptionLoggingMiddleware",
"social_django.middleware.SocialAuthExceptionMiddleware",
"middleware.remove_allow_header.RemoveAllowHeaderMiddleware",
]

PUBLIC_SCHEMA_URLCONF = "backend.public_urls"
ROOT_URLCONF = "backend.urls"
TENANT_SUBFOLDER_PREFIX = f"/{PATH_PREFIX}/unstract"
SHOW_PUBLIC_IF_NO_TENANT_FOUND = True

Expand Down Expand Up @@ -302,7 +361,7 @@ def get_required_setting(

DATABASES = {
"default": {
"ENGINE": "django_tenants.postgresql_backend",
"ENGINE": DB_ENGINE,
"NAME": f"{DB_NAME}",
"USER": f"{DB_USER}",
"HOST": f"{DB_HOST}",
Expand All @@ -315,8 +374,6 @@ def get_required_setting(
# SocketIO connection manager
SOCKET_IO_MANAGER_URL = f"redis://{REDIS_HOST}:{REDIS_PORT}"

DATABASE_ROUTERS = ("django_tenants.routers.TenantSyncRouter",)

CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
Expand Down Expand Up @@ -431,10 +488,7 @@ def get_required_setting(
SOCIAL_AUTH_EXTRA_DATA_EXPIRATION_TIME_IN_SECOND = os.environ.get(
"SOCIAL_AUTH_EXTRA_DATA_EXPIRATION_TIME_IN_SECOND", 3600
)
SOCIAL_AUTH_USER_MODEL = "account.User"
SOCIAL_AUTH_STORAGE = "connector_auth.models.ConnectorDjangoStorage"
SOCIAL_AUTH_JSONFIELD_ENABLED = True
SOCIAL_AUTH_URL_NAMESPACE = "social"
SOCIAL_AUTH_FIELDS_STORED_IN_SESSION = ["oauth-key", "connector-guid"]
SOCIAL_AUTH_TRAILING_SLASH = False

Expand All @@ -446,12 +500,12 @@ def get_required_setting(

SOCIAL_AUTH_PIPELINE = (
# Checks if user is authenticated
"connector_auth.pipeline.common.check_user_exists",
SOCIAL_AUTH_PIPELINE_USER_AUTH,
# Gets user details from provider
"social_core.pipeline.social_auth.social_details",
"social_core.pipeline.social_auth.social_uid",
# Cache secrets and fields in redis
"connector_auth.pipeline.common.cache_oauth_creds",
SOCIAL_AUTH_PIPELINE_CACHE_CRED,
)

# Social Auth: Google OAuth2
Expand Down
48 changes: 48 additions & 0 deletions backend/feature_flag/helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import logging

from unstract.flags.client.flipt import FliptClient
from unstract.flags.feature_flag import check_feature_flag_status

logger = logging.getLogger(
__name__,
)


class FeatureFlagHelper:
@staticmethod
def list_all_flags(
namespace_key: str,
) -> dict:
"""Fetch all flags from flipt.

Args:
namespace_key (str)

Returns:
dict
"""
try:
flipt_client = FliptClient()
response = flipt_client.list_feature_flags(
namespace_key=namespace_key,
)
return response
except Exception as e:
logger.error(
f"Error while listing flags for namespace {namespace_key}: {str(e)}"
)
return {}

@staticmethod
def check_flag_status(
flag_name: str,
) -> bool:
"""Check current status of flag
Args:
flag_name (str): feature flag

Returns:
bool: is active or not
"""
flag_enabled = check_feature_flag_status(flag_name)
return flag_enabled
6 changes: 3 additions & 3 deletions backend/feature_flag/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@

import logging

from feature_flag.helper import FeatureFlagHelper
from rest_framework import status, viewsets
from rest_framework.response import Response
from utils.request.feature_flag import check_feature_flag_status, list_all_flags

logger = logging.getLogger(__name__)

Expand All @@ -26,7 +26,7 @@ def evaluate(self, request):
status=status.HTTP_400_BAD_REQUEST,
)

flag_enabled = check_feature_flag_status(flag_key)
flag_enabled = FeatureFlagHelper.check_flag_status(flag_key)
return Response({"flag_status": flag_enabled}, status=status.HTTP_200_OK)
except Exception as e:
logger.error("No response from server: %s", e)
Expand All @@ -38,7 +38,7 @@ def evaluate(self, request):
def list(self, request):
try:
namespace_key = request.query_params.get("namespace", "default")
feature_flags = list_all_flags(namespace_key)
feature_flags = FeatureFlagHelper.list_all_flags(namespace_key)
return Response({"feature_flags": feature_flags}, status=status.HTTP_200_OK)
except Exception as e:
logger.error("No response from server: %s", e)
Expand Down
5 changes: 2 additions & 3 deletions backend/sample.env
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,8 @@ STRUCTURE_TOOL_IMAGE_NAME="unstract/tool-structure"
STRUCTURE_TOOL_IMAGE_TAG="0.0.28"

# Feature Flags
EVALUATION_SERVER_IP=localhost
EVALUATION_SERVER_PORT=9005
# Stubs generator
EVALUATION_SERVER_IP=unstract-flipt
EVALUATION_SERVER_PORT=9000
PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python


Expand Down
1 change: 1 addition & 0 deletions docker/dockerfiles/prompt.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ WORKDIR /app
COPY --chown=unstract ${BUILD_CONTEXT_PATH} .
# Copy local dependency packages
COPY --chown=unstract ${BUILD_PACKAGES_PATH}/core /unstract/core
COPY --chown=unstract ${BUILD_PACKAGES_PATH}/flags /unstract/flags

RUN set -e; \
\
Expand Down
1 change: 1 addition & 0 deletions docker/dockerfiles/prompt.Dockerfile.dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,6 @@ platform-service
tools
unstract
!unstract/core
!unstract/flags
worker
x2text-service
Loading