HEX
Server: Apache/2.4.46 (Win64) OpenSSL/1.1.1j PHP/8.4.25
System: Windows NT DESKTOP-4TAV2RJ 10.0 build 19045 (Windows 10) AMD64
User: fred (0)
PHP: 8.4.25
Disabled: NONE
Upload Files
File: C:/Users/fred/anaconda3/Lib/site-packages/anaconda_navigator/config/feature_flags.py
"""Module for managing feature flags"""
from __future__ import annotations

import base64
import dataclasses
import json
import sys
import typing
from urllib import parse

import anaconda_anon_usage.tokens as anon_tokens
from qtpy.QtCore import QObject, Signal, QTimer

from anaconda_navigator import __about__
from anaconda_navigator.api import cloud, conda_api
from anaconda_navigator.config import CONF, FEATURE_FLAGS_JSON_PATH
from anaconda_navigator.utils import download_manager
from anaconda_navigator.utils import singletons


def prepare_context() -> bytes:
    """Get context attributes."""
    return json.dumps({
        'key': anon_tokens.client_token() or 'default',
        'email': cloud.CloudAPI().username,
        'platform': conda_api.CondaAPI().get_platform(),
        'navigator_version': __about__.__version__
    }).encode()


def prepare_headers() -> typing.Mapping[str, bytes | str]:
    """Format header value of feature flag context."""
    return {'X-FEATUREFLAG-CONTEXT': base64.b64encode(prepare_context()).decode('ascii')}


def normalize_key(name: str) -> str:
    """Convert launchdarkly feature key name to python format."""
    return name.lower().replace('-', '_')


@dataclasses.dataclass(eq=False)
class FeatureFlags:
    """Feature flags store."""
    survey_url: str = ''
    known_channels: tuple[str, ...] = (
        'defaults', 'anaconda', 'astropy', 'beeware', 'binstar', 'biobuilds', 'bioconda', 'conda-forge', 'esri', 'free',
        'intel', 'main', 'menpo', 'msys2', 'omnia', 'pro', 'pytorch', 'qiime2', 'r', 'rapidsai', 'rapidsai-nightly',
        'rdkit', 'mgoetze', 'conda-test', 'conda-canary', 'anaconda-client-test', 'navigator-test'
    )
    login_reminder_interval_msec: int = 86400_000
    whats_new_enabled: bool = sys.platform != 'linux'


class FeatureFlagsManager(QObject):
    """Class for managing the loading and caching of feature flags."""

    sig_flags_loaded = Signal()

    def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
        """Initialize new :class:`~FeatureFlagsManager` instance."""
        super().__init__(*args, **kwargs)
        self.__timer: QTimer = QTimer()
        self.__loaded: bool = False
        self.__base_url = CONF.get('internal', 'launchdarkly_proxy_base_url')

    def __set_loaded(self, value: bool = True) -> None:
        self.__loaded = value
        self.sig_flags_loaded.emit()

    def load(self) -> None:
        """Load feature flags from a remote source to the cache file."""
        if not self.__base_url:
            self.__load_cached()
            return

        self.__timer.singleShot(5_000, self.__load_cached)
        download: download_manager.Download = download_manager.Download(
            parse.urljoin(self.__base_url, 'v1/feature-flags')
        ).into(FEATURE_FLAGS_JSON_PATH).attach('POST', prepare_context())
        download.sig_succeeded.connect(self.__load_cached)
        download_manager.Manager().execute(download)

    def __load_cached(self) -> None:
        """Load feature flags from the cache file."""
        self.__timer.stop()

        if self.__loaded:
            return

        try:
            stream: typing.TextIO
            with open(FEATURE_FLAGS_JSON_PATH, 'rt', encoding='utf-8') as stream:
                cached_flags = json.load(stream)
                for flag_key, value in cached_flags.items():
                    normal_flag_key: str = normalize_key(flag_key)
                    if hasattr(FEATURE_FLAGS, normal_flag_key):
                        setattr(FEATURE_FLAGS, normal_flag_key, value)
        except (OSError, TypeError, ValueError):
            pass
        finally:
            self.__set_loaded()


FEATURE_FLAGS: FeatureFlags = FeatureFlags()
FEATURE_FLAGS_MANAGER: typing.Final[
    singletons.Singleton[FeatureFlagsManager]] = singletons.SingleInstanceOf(FeatureFlagsManager)