Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Impression events for ucp #243

Merged
merged 10 commits into from
Apr 6, 2023
Merged
Show file tree
Hide file tree
Changes from 4 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
10 changes: 10 additions & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# [Choice] Python version (use -bullseye variants on local arm64/Apple Silicon): 3, 3.10, 3.9, 3.8, 3.7, 3.6, 3-bullseye, 3.10-bullseye, 3.9-bullseye, 3.8-bullseye, 3.7-bullseye, 3.6-bullseye, 3-buster, 3.10-buster, 3.9-buster, 3.8-buster, 3.7-buster, 3.6-buster
ARG VARIANT=3.7
FROM mcr.microsoft.com/vscode/devcontainers/python:${VARIANT}

# [Choice] Node.js version: none, lts/*, 16, 14, 12, 10
ARG NODE_VERSION="none"
RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi

# Update pip
RUN pip install -U pip
65 changes: 65 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{
"name": "Python 3",
"build": {
"dockerfile": "Dockerfile",
"context": "..",
"args": {
// Update 'VARIANT' to pick a Python version: 3, 3.10, 3.9, 3.8, 3.7, 3.6
// Append -bullseye or -buster to pin to an OS version.
// Use -bullseye variants on local on arm64/Apple Silicon.
"VARIANT": "3.10",
// Options
"NODE_VERSION": "lts/*"
}
},

// Configure tool-specific properties.
"customizations": {
// Configure properties specific to VS Code.
"vscode": {
// Set *default* container specific settings.json values on container create.
"settings": {
"python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python",
"python.formatting.blackPath": "${workspaceFolder}/.venv/bin/black",
"python.linting.enabled": true,
"python.linting.pylintEnabled": true,
"python.linting.flake8Path": "${workspaceFolder}/.venv/bin/flake8",
"python.linting.mypyPath": "${workspaceFolder}/.venv/bin/mypy",
"python.linting.pylintPath": "${workspaceFolder}/.venv/bin/pylint",
"python.testing.pytestArgs": [
"tests"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
"python.venvPath": "${workspaceFolder}/.venv"
},

// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"ms-python.python",
"ms-python.vscode-pylance",
"ms-azuretools.vscode-docker",
ivanklee86 marked this conversation as resolved.
Show resolved Hide resolved
"ritwickdey.LiveServer",
"ms-vscode.makefile-tools",
"bungcip.better-toml",
"ms-python.vscode-pylance"
]
}
},

// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],

// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "bash ./.devcontainer/post_install.sh",

"features": {
"ghcr.io/devcontainers/features/docker-in-docker:2": {
"dockerDashComposeVersion": "v2"
}
},

// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode"
}

20 changes: 20 additions & 0 deletions .devcontainer/post_install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/bash
set -ex

# Install package
sudo python -m venv .venv
source .venv/bin/activate
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

22% of developers fix this issue

SC1091: Not following: .venv/bin/activate was not specified as input (see shellcheck -x).


ℹ️ Expand to see all @sonatype-lift commands

You can reply with the following commands. For example, reply with @sonatype-lift ignoreall to leave out all findings.

Command Usage
@sonatype-lift ignore Leave out the above finding from this PR
@sonatype-lift ignoreall Leave out all the existing findings from this PR
@sonatype-lift exclude <file|issue|path|tool> Exclude specified file|issue|path|tool from Lift findings by updating your config.toml file

Note: When talking to LiftBot, you need to refresh the page to see its response.
Click here to add LiftBot to another repo.


Help us improve LIFT! (Sonatype LiftBot external survey)

Was this a good recommendation for you? Answering this survey will not impact your Lift settings.

[ 🙁 Not relevant ] - [ 😕 Won't fix ] - [ 😑 Not critical, will fix ] - [ 🙂 Critical, will fix ] - [ 😊 Critical, fixing now ]

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sonatype-lift ignore

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've recorded this as ignored for this pull request.
If you change your mind, just comment @sonatype-lift unignore.

pip install -U -r requirements.txt
python setup.py install
./get-spec.sh

# Install pre-config
# pip install pre-commit
# pre-commit install
# pre-commit

# Configure git
git config --global --add --bool push.autoSetupRemote true

# Woohoo!
echo "Hooray, it's done!"
43 changes: 38 additions & 5 deletions UnleashClient/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import warnings
import random
import string
import uuid
from datetime import datetime, timezone
from typing import Callable, Optional
from apscheduler.job import Job
Expand All @@ -15,6 +16,7 @@
GradualRolloutSessionId, GradualRolloutUserId, UserWithId, RemoteAddress, FlexibleRollout
from UnleashClient.constants import METRIC_LAST_SENT_TIME, DISABLED_VARIATION, ETAG
from UnleashClient.loader import load_features
from UnleashClient.events import UnleashEvent, UnleashEventType
from .utils import LOGGER, InstanceCounter, InstanceAllowType
from .deprecation_warnings import strategy_v2xx_deprecation_check
from .cache import BaseCache, FileCache
Expand Down Expand Up @@ -45,6 +47,7 @@ class UnleashClient:
:param scheduler: Custom APScheduler object. Use this if you want to customize jobstore or executors. When unset, UnleashClient will create it's own scheduler.
:param scheduler_executor: Name of APSCheduler executor to use if using a custom scheduler.
:param multiple_instance_mode: Determines how multiple instances being instantiated is handled by the SDK, when set to InstanceAllowType.BLOCK, the client constructor will fail when more than one instance is detected, when set to InstanceAllowType.WARN, multiple instances will be allowed but log a warning, when set to InstanceAllowType.SILENTLY_ALLOW, no warning or failure will be raised when instantiating multiple instances of the client. Defaults to InstanceAllowType.WARN
:param event_callback: Function to call if impression events are enabled.
"""
def __init__(self,
url: str,
Expand All @@ -66,7 +69,8 @@ def __init__(self,
cache: Optional[BaseCache] = None,
scheduler: Optional[BaseScheduler] = None,
scheduler_executor: Optional[str] = None,
multiple_instance_mode: InstanceAllowType = InstanceAllowType.WARN) -> None:
multiple_instance_mode: InstanceAllowType = InstanceAllowType.WARN,
event_callback: Optional[Callable[[UnleashEvent], None]] = None) -> None:
custom_headers = custom_headers or {}
custom_options = custom_options or {}
custom_strategies = custom_strategies or {}
Expand All @@ -90,6 +94,7 @@ def __init__(self,
}
self.unleash_project_name = project_name
self.unleash_verbose_log_level = verbose_log_level
self.unleash_event_callback = event_callback

self._do_instance_check(multiple_instance_mode)

Expand Down Expand Up @@ -294,7 +299,21 @@ def is_enabled(self,

if self.unleash_bootstrapped or self.is_initialized:
try:
return self.features[feature_name].is_enabled(context)
feature = self.features[feature_name]
feature_check = feature.is_enabled(context)

if self.unleash_event_callback and feature.impression_data:
event = UnleashEvent(
event_type=UnleashEventType.FEATURE_FLAG,
event_id=uuid.uuid1(),
context=context,
enabled=feature_check,
feature_name=feature_name
)

self.unleash_event_callback(event)

return feature_check
except Exception as excep:
LOGGER.log(self.unleash_verbose_log_level, "Returning default value for feature: %s", feature_name)
LOGGER.log(self.unleash_verbose_log_level, "Error checking feature flag: %s", excep)
Expand Down Expand Up @@ -324,7 +343,22 @@ def get_variant(self,

if self.unleash_bootstrapped or self.is_initialized:
try:
return self.features[feature_name].get_variant(context)
feature = self.features[feature_name]
variant_check = feature.get_variant(context)

if self.unleash_event_callback and feature.impression_data:
event = UnleashEvent(
event_type=UnleashEventType.VARIANT,
event_id=uuid.uuid1(),
context=context,
enabled=variant_check['enabled'],
feature_name=feature_name,
variant=variant_check['enabled']
)

self.unleash_event_callback(event)

return variant_check
except Exception as excep:
LOGGER.log(self.unleash_verbose_log_level, "Returning default flag/variation for feature: %s", feature_name)
LOGGER.log(self.unleash_verbose_log_level, "Error checking feature flag variant: %s", excep)
Expand All @@ -339,8 +373,7 @@ def _do_instance_check(self, multiple_instance_mode):
if identifier in INSTANCES:
msg = f"You already have {INSTANCES.count(identifier)} instance(s) configured for this config: {identifier}, please double check the code where this client is being instantiated."
if multiple_instance_mode == InstanceAllowType.BLOCK:
# pylint: disable=broad-exception-raised
raise Exception(msg)
raise Exception(msg) # pylint: disable=broad-exception-raised
if multiple_instance_mode == InstanceAllowType.WARN:
LOGGER.error(msg)
INSTANCES.increment(identifier)
Expand Down
3 changes: 1 addition & 2 deletions UnleashClient/api/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,7 @@ def get_feature_toggles(url: str,
if resp.status_code not in [200, 304]:
log_resp_info(resp)
LOGGER.warning("Unleash Client feature fetch failed due to unexpected HTTP status code.")
# pylint: disable=broad-exception-raised
raise Exception("Unleash Client feature fetch failed!")
raise Exception("Unleash Client feature fetch failed!") # pylint: disable=broad-exception-raised
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

E261: at least two spaces before inline comment


ℹ️ Expand to see all @sonatype-lift commands

You can reply with the following commands. For example, reply with @sonatype-lift ignoreall to leave out all findings.

Command Usage
@sonatype-lift ignore Leave out the above finding from this PR
@sonatype-lift ignoreall Leave out all the existing findings from this PR
@sonatype-lift exclude <file|issue|path|tool> Exclude specified file|issue|path|tool from Lift findings by updating your config.toml file

Note: When talking to LiftBot, you need to refresh the page to see its response.
Click here to add LiftBot to another repo.


Help us improve LIFT! (Sonatype LiftBot external survey)

Was this a good recommendation for you? Answering this survey will not impact your Lift settings.

[ 🙁 Not relevant ] - [ 😕 Won't fix ] - [ 😑 Not critical, will fix ] - [ 🙂 Critical, will fix ] - [ 😊 Critical, fixing now ]


etag = ''
if 'etag' in resp.headers.keys():
Expand Down
19 changes: 19 additions & 0 deletions UnleashClient/events.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from typing import Optional
from dataclasses import dataclass
from enum import Enum
from uuid import UUID


class UnleashEventType(Enum):
FEATURE_FLAG = "feature_flag"
VARIANT = "variant"


@dataclass
class UnleashEvent:
event_type: UnleashEventType
event_id: UUID
context: dict
enabled: bool
feature_name: str
variant: Optional[str] = ''