Flask/Python에서 자체 기능 플래그 관리자 구축
31055 단어 pythonfeatureflagsdevtoolsflask
특성 표지는 본질적으로 좋은
if
/else
검사 방법으로 사용자에게 어떤 코드 경로를 사용할지 선택할 수 있지만 그것들을 관리하는 약간의 도구라도 개발 속도를 완전히 높일 수 있다.너무 좋아요!이것은 사용자가 더 많은 유용한 제품을 더 빨리 얻을 수 있다는 것을 의미하기를 바란다.특성 표지의 첫 번째 큰일은 사용자가 새로운 특성의 증량 개발을 할 수 있다는 것이다. 단독의 특성 지점에 대해 논쟁할 필요가 없다는 것이다.부분적으로 완성된 작업을 특성 표지 뒤에 숨기면main을 수시로 제출할 수 있습니다.만약 네가 다른 사람과 합작한다면, 이 점은 특히 유용하다.
if feature_flag('NEW_FEATURE', 'ENABLED'):
# show new feature
새로운 기능, 디자인, 변체를 발표하려고 준비할 때, 선택적으로 이렇게 할 수도 있습니다. 변체 그룹에 인원을 추가하여 기능 로고가 그것을 보기 시작하도록 합니다.특성 로고가 있어 새로운 특성의 출시는 코드 배치와 분리될 수 있어 더욱 안전하고 문제가 발생하면 복구하기 쉽다.if feature_flag('MY_EXPERIMENT', 'VARIANT_A'):
# show variant a
elif feature_flag('MY_EXPERIMENT', 'VARIANT_B'):
# show variant b
else:
# show control
사든지 만들든지
구매와 건설 결정에 있어서 사람들은 나보다 더 똑똑하고 경험이 많다. 예를 들어 라는 책의 저자인 카밀 포니어(Camille Fournier)의 이 우수한 문장analysis이다.나는 여기서 전체 주제를 반복하지는 않지만, 엔지니어들이 일반적으로 그것을 선택하는 경향이 있는 것보다 정확한 결정은 구매라는 것을 말하고 싶다.적어도 어떤 규모의 생산 시스템에서도 공사팀은 마찬가지다.
본고를 작성할 때까지 2021년 동안 LaunchDarkly는 선도적인 기능 표지인 SaaS 솔루션이었다.그들의 업무 규모를 바탕으로 많은 개발자들이 좋은 시스템에서 그들의 기능 표지를 관리하는 가치를 발견한 것 같다.사용자당 10달러의 가격으로 기존의 해결 방안을 사용하는 것은 공사 시간을 소비하는 것보다 훨씬 싸다(비용이 훨씬 높다!)자신의 집을 짓다.
그럼에도 불구하고 기능 로고의 기본 버전은 상대적으로 실현하기 쉽다. 만약에 당신이 새로운 프로젝트에 종사하고 있다면 외부 서비스와 통합하는 번거로움을 원하지 않을 수도 있고 개발자마다 매달 비용을 지불해서 블러드 값을 검사할 수 있기를 원하지 않을 수도 있다.당신이나 기능 로고 관리자의 실현을 궁금하게 생각하고 있다면 계속 읽어 주십시오.
자체 기능 플래그 관리자 구축
나는 줄곧 기능 표지 관리자의 추상화를'문지기'라고 불렀는데 페이스북과 많은 전 페이스북 사용자들에게서 일했다. 왜냐하면 이것은 그들의 기능 표지와 실험의 내부 서비스 이름framework이기 때문이다.이것은 기억하기 쉬운 이름이며 코드에서 쉽게 줄여서
gk
라고 할 수 있다.나도 여기서 쓸 거야.gatekeeper 자체를 제외하고는 기능 표지가 무엇인지, 사용자나 세션을 다른 변체에 넣는 논리를 지정하기 위해gatekeeper 설정이 필요합니다.가장 전문적인 기능 로고 솔루션은 코드에 독립된 프로필을 사용하지만, 우리가 개발한 첫 번째 버전의gatekeeper에 대해 프로필을 코드에 넣는 것이 가장 간단하고, 작업도 충분하다.
마지막으로gatekeeper를 초기화하고 등록해서 개발자에게 사용하도록 하는 것이 개발자 도구에 있어서 매우 관건적입니다!일을 가능한 한 쉽게 사용하기 위해서, 나는 핵심 추상적인 또 다른 추가 기능이 매우 유용하다는 것을 발견했다. 그것은 바로 브라우저 덮어쓰기를 지원하는 것이다. 이것은 관리 계기판을 통해 설정할 수 있다는 것이다.
어쨌든 당신이 필요로 하는 것은:
브라우저 덮어쓰기를 지원하는 코드는 포함되어 있지만 덮어쓰기를 설정하고 읽을 수 있는 관리 대시보드 코드는 포함되어 있지 않습니다.
골키퍼py
from gatekeeper_config import FF_CONFIG_MAP
class Gatekeeper(object):
def __init__(self, user_id=None,
app=None,
request=None,
session=None,
config_map=None):
self.user_id = user_id
self.app = app
self.request = request
self.session = session
self.config_map = config_map if config_map else FF_CONFIG_MAP
def ff(self, *args, **kwargs):
'''Shorthand wrapper for `feature_flag`.'''
return self.feature_flag(*args, **kwargs)
def feature_flag(self, flag, variant):
return self.get_feature_flag_variant(flag) == variant
def ff_variant(self, *args, **kwargs):
'''Shorthand wrapper for `get_feature_flag_variant`.'''
return self.get_feature_flag_variant(*args, **kwargs)
def get_feature_flag_variant(self, flag, user_id_override):
config = self.config_map.get(flag)
if not config:
return None
variant = config.get_variant(
user_id=user_id_override or self.user_id,
app=self.app,
request=self.request,
session=self.session)
return variant.name
def get_config_map(self):
return self.config_map
def get_browser_override_variants(self, request):
return json.loads(request.cookies.get('gatekeeper', '{}'))
def set_browser_override_variant(self, request, flag, variant):
config = self.config_map.get(flag)
if not config:
return None
return config.set_browser_override_variant(request, variant)
def initialize_gatekeeper(user_id=None, app=None, config_map=None):
from flask import current_app
from flask import request
from flask import session
from flask_login import current_user
if not app:
app = current_app
if user_id is None and current_user and not current_user.is_anonymous:
user_id = current_user.id
gk = Gatekeeper(
user_id=user_id,
app=app,
request=request,
session=session,
config_map=config_map)
return gk
gatekeeper_config.py
from abc import ABC
from abc import abstractmethod
from enum import Enum
import json
import flask
class FeatureFlagConfig(ABC):
FLAG_NAME: str
VARIANTS_ENUM_STR: str
def __init__(self, overrides=None):
self.variants_enum = Enum(
f'{self.__class__.__name__}Variants', self.VARIANTS_ENUM_STR)
self.overrides = overrides
def get_variants(self):
return list(self.variants_enum.__members__.keys())
@abstractmethod
def _get_variant(
self,
user_id: Optional[int] = None,
app: Optional[Flask] = None,
request: Optional[Request] = None,
session: Optional[Any] = None):
pass
def get_variant(self, user_id: Optional[int] = None, app: Optional[Flask] = None,
request: Optional[Request] = None, session: Optional[Any] = None):
override_variant = self.get_override_variant(
user_id=user_id, app=app, request=request, session=session)
if override_variant:
return override_variant
else:
return self._get_variant(
user_id=user_id, app=app, request=request, session=session)
def get_override_variant(
self, user_id=None, app=None, request=None, session=None):
if request:
browser_override_variant = self.get_browser_override_variant(request)
if browser_override_variant:
return browser_override_variant
if self.overrides:
for variant, user_ids in self.overrides.items():
if user_id in user_ids:
return self.variants_enum[variant]
return None
def set_browser_override_variant(self, request, variant):
browser_override_variants = self.get_browser_override_variants(request)
if variant == '':
browser_override_variants.pop(self.FLAG_NAME, None)
else:
browser_override_variants[self.FLAG_NAME] = variant
response = flask.make_response()
response.set_cookie(
'gatekeeper',
json.dumps(browser_override_variants))
return response
def get_browser_override_variants(self, request):
return json.loads(request.cookies.get('gatekeeper', '{}'))
def get_browser_override_variant(self, request):
browser_override_variants = self.get_browser_override_variants(request)
browser_override_variant = browser_override_variants.get(self.FLAG_NAME)
if browser_override_variant:
return self.variants_enum[browser_override_variant]
else:
return None
class MyNewFeatureFFConfig(FeatureFlagConfig):
FLAG_NAME = 'MY_NEW_FEATURE'
VARIANTS_ENUM_STR = 'VISIBLE NOT_VISIBLE'
DESCRIPTION = 'Gate visibility of my new feature during development'
def _get_variant(self, user_id=None, app=None, request=None, session=None):
if app and app.config.get('IS_DEV'):
return self.variants_enum.VISIBLE
elif user_id and user_id in [1, 2, 3]: # Team user ids
return self.variants_enum.VISIBLE
else:
return self.variants_enum.NOT_VISIBLE
FF_CONFIG_MAP: FFConfigMap = {
'MY_NEW_FEATURE': MyNewFeatureFFConfig()
}
년
app.py
# All the usual app setup stuff here...
app = create_app()
@app.before_request
def register_gatekeeper():
# Put imports here to avoid circular import issues.
from flask import request
import gatekeeper
request.gk = gatekeeper.initialize_gatekeeper(app=app)
골키퍼를 사용하다
이 모든 설정이 있으면, 현재gatekeeper를 검색해서 특성 표지와 변체를 얻는 것은 매우 간단합니다.
예를 들어, Python 웹 프로세서 코드에서는 다음과 같이 보일 수 있습니다.
if request.gk.ff('MY_NEW_FEATURE', 'VISIBLE'):
# Show new feature
또는 Jinja 템플릿 코드에서는 다음과 같이 보일 수 있습니다.{% if request.gk.ff('MY_NEW_FEATURE', 'VISIBLE') %}
{# Show new feature #}
{% endif %}
마지막 생각
덮어쓰기, 특히 브라우저 덮어쓰기를 지원하기 위해 코드를 꺼내면, 기능적인 기능 로고 관리자를 만들 필요가 거의 없다는 것을 알게 될 것이다.제가 덮어쓰기 지원을 포함하는 이유는 개발자의 용이성에 매우 중요하고 예상보다 중요하다는 것을 발견했기 때문입니다.
만약 기능 로고를 사용하지 않았다면, 이 글은 개발과 발표 작업 흐름에서 이 로고를 사용하도록 설득할 수 있기를 바랍니다. 만약 다른 SaaS 솔루션을 만들고 싶지 않다면, 아마도 여기에 공유된 코드는 사용자가 자신의 기능 로고 관리자를 사용하기 시작하는 데 도움을 줄 수 있을 것입니다.즐거운 코딩!
Reference
이 문제에 관하여(Flask/Python에서 자체 기능 플래그 관리자 구축), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/triketora/build-your-own-feature-flags-manager-in-flask-python-1jo0텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)