def is_enabled(self, context: dict = None, default_value: bool = False) -> bool: # pylint: disable=unused-argument """ Checks if feature is enabled. :param context: Context information :param default_value: Deprecated! Users should use the fallback_function on the main is_enabled() method. :return: """ flag_value = False if self.enabled: try: if self.strategies: strategy_result = any(x.execute(context) for x in self.strategies) else: # If no strategies are present, should default to true. This isn't possible via UI. strategy_result = True flag_value = strategy_result except Exception as strategy_except: LOGGER.warning("Error checking feature flag: %s", strategy_except) self.increment_stats(flag_value) LOGGER.info("Feature toggle status for feature %s: %s", self.name, flag_value) return flag_value
def is_enabled(self, context: dict = None, default_value: bool = False) -> bool: """ Checks if feature is enabled. :param context: Context information :param default_value: Optional, but allows for override. :return: """ flag_value = default_value if self.enabled: try: if self.strategies: strategy_result = any( [x.execute(context) for x in self.strategies]) else: # If no strategies are present, should default to true. This isn't possible via UI. strategy_result = True flag_value = flag_value or strategy_result except Exception as strategy_except: LOGGER.warning("Error checking feature flag: %s", strategy_except) self.increment_stats(flag_value) LOGGER.info( f"Feature toggle status for feature {self.name} with context {context} : {flag_value}" ) return flag_value
def is_enabled(self, context: dict = None, default_value: bool = False) -> bool: """ Checks if feature is enabled. :param context: Context information :param default_value: Optional, but allows for override. :return: """ flag_value = default_value if self.enabled: try: for strategy in self.strategies: flag_value = flag_value or strategy(context) except Exception as strategy_except: LOGGER.warning("Error checking feature flag: %s", strategy_except) self.increment_stats(flag_value) LOGGER.info("Feature toggle status for feature %s: %s", self.name, flag_value) return flag_value
def get_feature_toggles(url: str, app_name: str, instance_id: str, custom_headers: dict, custom_options: dict, project: str = None) -> dict: """ Retrieves feature flags from unleash central server. Notes: * If unsuccessful (i.e. not HTTP status code 200), exception will be caught and logged. This is to allow "safe" error handling if unleash server goes down. :param url: :param app_name: :param instance_id: :param custom_headers: :param custom_options: :param project: :return: Feature flags if successful, empty dict if not. """ try: LOGGER.info("Getting feature flag.") headers = { "UNLEASH-APPNAME": app_name, "UNLEASH-INSTANCEID": instance_id } base_url = f"{url}{FEATURES_URL}" base_params = {} if project: base_params = {'project': project} resp = requests.get(base_url, headers={ **custom_headers, **headers }, params=base_params, timeout=REQUEST_TIMEOUT, **custom_options) if resp.status_code != 200: log_resp_info(resp) LOGGER.warning( "Unleash Client feature fetch failed due to unexpected HTTP status code." ) raise Exception("Unleash Client feature fetch failed!") return resp.json() except Exception as exc: LOGGER.exception( "Unleash Client feature fetch failed due to exception: %s", exc) return {}
def apply(self, context: dict = None) -> bool: """ Returns true/false depending on constraint provisioning and context. :param context: Context information :return: """ constraint_check = False try: context_value = get_identifier(self.context_name, context) # Set currentTime if not specified if self.context_name == "currentTime" and not context_value: context_value = datetime.now() if context_value is not None: if self.operator in [ ConstraintOperators.IN, ConstraintOperators.NOT_IN ]: constraint_check = self.check_list_operators( context_value=context_value) elif self.operator in [ ConstraintOperators.STR_CONTAINS, ConstraintOperators.STR_ENDS_WITH, ConstraintOperators.STR_STARTS_WITH ]: constraint_check = self.check_string_operators( context_value=context_value) elif self.operator in [ ConstraintOperators.NUM_EQ, ConstraintOperators.NUM_GT, ConstraintOperators.NUM_GTE, ConstraintOperators.NUM_LT, ConstraintOperators.NUM_LTE ]: constraint_check = self.check_numeric_operators( context_value=context_value) elif self.operator in [ ConstraintOperators.DATE_AFTER, ConstraintOperators.DATE_BEFORE ]: constraint_check = self.check_date_operators( context_value=context_value) elif self.operator in [ ConstraintOperators.SEMVER_EQ, ConstraintOperators.SEMVER_GT, ConstraintOperators.SEMVER_LT ]: constraint_check = self.check_semver_operators( context_value=context_value) except Exception as excep: # pylint: disable=broad-except LOGGER.info("Could not evaluate context %s! Error: %s", self.context_name, excep) return not constraint_check if self.inverted else constraint_check
def fetch_and_load_features(url, app_name, instance_id, custom_headers, cache, features, strategy_mapping): feature_provisioning = get_feature_toggles(url, app_name, instance_id, custom_headers) if feature_provisioning: cache[FEATURES_URL] = feature_provisioning cache.sync() else: LOGGER.info("Unable to get feature flag toggles, using cached values.") load_features(cache, features, strategy_mapping)
def fetch_and_load_features(url: str, app_name: str, instance_id: str, custom_headers: dict, cache: FileCache, strategies: dict) -> None: feature_provisioning = get_feature_toggles(url, app_name, instance_id, custom_headers) if feature_provisioning: cache[FEATURES_URL] = feature_provisioning cache.sync() else: LOGGER.info("Unable to get feature flag toggles, using cached values.") load_features(cache, strategies)
def apply(self, context=None): """ Returns true/false depending on constraint provisioning and context. :param context: Context information :return: """ constraint_check = False try: value = get_identifier(self.context_name, context) if value: if self.operator.upper() == "IN": constraint_check = value in self.values elif self.operator.upper() == "NOT_IN": constraint_check = value not in self.values except Exception as excep: #pylint: disable=W0703 LOGGER.info("Could not evaluate context %s! Error: %s", self.context_name, excep) return constraint_check
def get_feature_toggles(url: str, app_name: str, instance_id: str, custom_headers: dict) -> dict: """ Retrieves feature flags from unleash central server. Notes: * If unsuccessful (i.e. not HTTP status code 200), exception will be caught and logged. This is to allow "safe" error handling if unleash server goes down. :param url: :param app_name: :param instance_id: :param custom_headers: :return: Feature flags if successful, empty dict if not. """ try: LOGGER.info("Getting feature flag.") headers = { "UNLEASH-APPNAME": app_name, "UNLEASH-INSTANCEID": instance_id } resp = requests.get(url + FEATURES_URL, headers={ **custom_headers, **headers }, timeout=REQUEST_TIMEOUT) if resp.status_code != 200: LOGGER.warning("unleash feature fetch failed!") raise Exception("unleash feature fetch failed!") return json.loads(resp.content) except Exception: LOGGER.exception("Unleash feature fetch failed!") return {}
def register_client(url: str, app_name: str, instance_id: str, metrics_interval: int, custom_headers: dict, custom_options: dict, supported_strategies: dict) -> bool: """ Attempts to register client with unleash server. Notes: * If unsuccessful (i.e. not HTTP status code 202), exception will be caught and logged. This is to allow "safe" error handling if unleash server goes down. :param url: :param app_name: :param instance_id: :param metrics_interval: :param custom_headers: :param custom_options: :param supported_strategies: :return: true if registration successful, false if registration unsuccessful or exception. """ registation_request = { "appName": app_name, "instanceId": instance_id, "sdkVersion": "{}:{}".format(SDK_NAME, SDK_VERSION), "strategies": [*supported_strategies], "started": datetime.now(timezone.utc).isoformat(), "interval": metrics_interval } try: LOGGER.info("Registering unleash client with unleash @ %s", url) LOGGER.info("Registration request information: %s", registation_request) resp = requests.post(url + REGISTER_URL, data=json.dumps(registation_request), headers={ **custom_headers, **APPLICATION_HEADERS }, timeout=REQUEST_TIMEOUT, **custom_options) if resp.status_code != 202: log_resp_info(resp) LOGGER.warning( "Unleash Client registration failed due to unexpected HTTP status code." ) return False LOGGER.info("Unleash Client successfully registered!") return True except Exception: LOGGER.exception( "Unleash Client registration failed due to exception: %s", Exception) return False
def update_cache(data: Dict[str, Any]) -> None: """ Update cache data Args: data(dict): Feature toggles Data Returns: None """ if FeatureToggles.__cache is None: raise Exception( 'To update cache Feature Toggles class needs to be initialised' ) LOGGER.info(f'Updating the cache data: {data}') try: FeatureToggles.__cache.set(consts.FEATURES_URL, pickle.dumps(data)) except Exception as err: raise Exception( f'Exception occured while updating the redis cache: {str(err)}' ) LOGGER.info(f'Cache Updatation is Done')
def send_metrics(url: str, request_body: dict, custom_headers: dict, custom_options: dict) -> bool: """ Attempts to send metrics to Unleash server Notes: * If unsuccessful (i.e. not HTTP status code 200), message will be logged :param url: :param app_name: :param instance_id: :param metrics_interval: :param custom_headers: :param custom_options: :return: true if registration successful, false if registration unsuccessful or exception. """ try: LOGGER.info("Sending messages to with unleash @ %s", url) LOGGER.info("unleash metrics information: %s", request_body) resp = requests.post(url + METRICS_URL, data=json.dumps(request_body), headers={ **custom_headers, **APPLICATION_HEADERS }, timeout=REQUEST_TIMEOUT, **custom_options) if resp.status_code != 202: log_resp_info(resp) LOGGER.warning("Unleash CLient metrics submission failed.") return False LOGGER.info("Unleash Client metrics successfully sent!") return True except Exception: LOGGER.exception( "Unleash Client metrics submission failed dye to exception: %s", Exception) return False
def send_metrics(url, request_body, custom_headers): """ Attempts to send metrics to Unleash server Notes: * If unsuccessful (i.e. not HTTP status code 200), message will be logged :param url: :param app_name: :param instance_id: :param metrics_interval: :param custom_headers: :return: true if registration successful, false if registration unsuccessful or exception. """ try: LOGGER.info("Sending messages to with unleash @ %s", url) LOGGER.info("unleash metrics information: %s", request_body) headers = APPLICATION_HEADERS.copy() headers.update(custom_headers) resp = requests.post(url + METRICS_URL, data=json.dumps(request_body), headers=headers, timeout=REQUEST_TIMEOUT) if resp.status_code != 202: LOGGER.warning("unleash metrics submission failed.") return False LOGGER.info("unleash metrics successfully sent!") return True except Exception: LOGGER.exception("unleash metrics failed to send.") return False
def get_feature_toggles(url: str, app_name: str, instance_id: str, custom_headers: dict, custom_options: dict, project: str = None, cached_etag: str = '') -> Tuple[dict, str]: """ Retrieves feature flags from unleash central server. Notes: * If unsuccessful (i.e. not HTTP status code 200), exception will be caught and logged. This is to allow "safe" error handling if unleash server goes down. :param url: :param app_name: :param instance_id: :param custom_headers: :param custom_options: :param project: :param cached_etag: :return: (Feature flags, etag) if successful, ({},'') if not """ try: LOGGER.info("Getting feature flag.") headers = { "UNLEASH-APPNAME": app_name, "UNLEASH-INSTANCEID": instance_id } if cached_etag: headers['If-None-Match'] = cached_etag base_url = f"{url}{FEATURES_URL}" base_params = {} if project: base_params = {'project': project} resp = requests.get(base_url, headers={ **custom_headers, **headers }, params=base_params, timeout=REQUEST_TIMEOUT, **custom_options) 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." ) raise Exception("Unleash Client feature fetch failed!") etag = '' if 'etag' in resp.headers.keys(): etag = resp.headers['etag'] if resp.status_code == 304: return None, etag return resp.json(), etag except Exception as exc: LOGGER.exception( "Unleash Client feature fetch failed due to exception: %s", exc) return {}, ''