客户端到边沟功能交换机后端
gutter的Python项目详细描述
…图片:https://api.travis-ci.org/disks/gutter.png?branch=master
:目标:http://travis ci.org/disks/gutter
gutter
----
**注意:**此回购是Gargoyle 2的客户端,称为"gutter"。它不能与现有的"gargoyle 1代码库"一起使用,https://github.com/disks/gargoyle/>;` `.
gutter是功能切换管理库。它允许用户创建功能开关和这些开关将启用的设置条件。配置后,可以根据输入(请求、用户对象等)检查开关,以查看开关是否处于活动状态。
有关使用"Gutter django项目"配置Gutter的UI,请参阅https://github.com/disfs/Gutter django>;`
实用程序`
配置
==
Gutter在使用前需要进行少量配置。
它是一个"dict"或任何提供"types.mappingtype"接口的对象(```````setitem``和```getitem``方法)。默认情况下,``gutter``使用来自'durabledict library<;https://github.com/disks/durabledict>;``的'memorydict'实例。此引擎**不会在进程结束后保留数据**,因此应使用更持久的数据存储。
~~~~~~~~~~
``gutter``还可以"autocreate"开关。如果启用了"autocreate",并且询问"gutter"开关是否处于活动状态但尚未创建开关,则"gutter"将自动创建开关。自动创建时,开关的状态设置为"禁用"。
有关下面"设置"的更多信息。
配置设置
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>若要更改"存储"和/或"自动创建"设置,只需导入设置模块并设置相应的变量:
。代码::python
redisclient())
manager_settings.autocreate=true
在本例中,我们将引擎更改为durabledict的"redisdict",并打开"autocreate"。然后,这些设置将应用于所有新构建的"manager"实例。关于"manager"是什么以及稍后在本文中如何使用它的更多信息。
setup
=
一旦配置了"manager"的存储引擎,就可以导入gutter的默认"manager"对象,它是您与"gutter"的主界面:
。code::python
from gutter.client.default import gutter
此时,``gutter`对象是``manager``类的一个实例,该类保存所有注册开关的方法,并检查它们是否处于活动状态。在大多数安装和使用场景中,"gutter.client.gutter"管理器将是您的主界面。
`` gutter.client.gutter``,您可以构造一个``manager``实例,然后将其分配给``settings.manager.default``值:
。代码::python
from gutter.client.settings导入manager as manager_settings
from gutter.client.models导入manager
manager_settings.default=manager({})必须在从gutter.client.default导入默认管理器之前完成槽
assert manager\u settings.默认值为gutter
。警告:
:警告::警告:
请注意,在**导入默认"gutter"实例之前,必须设置"settings.manager.default"值**。
:警告::警告:
参数
==
您将要检查的参数。"参数"是一个对象,它了解系统中的业务逻辑和对象(用户、请求等),并知道如何验证、转换和提取这些业务对象中的变量,以满足"切换"条件。例如,您的系统可能有一个"user"对象,该对象具有诸如"is-admin"、"date-joined"等属性。若要与之进行切换,则需要为每个值创建参数。
若要执行此操作,请构造一个继承自"gutter.client.arguments.container"的类。在类的主体中,通过使用"gutter.client.arguments"函数可以创建所需的类变量"arguments"。
代码::python
from gutter.client import arguments
from myapp import user
class userarguments(arguments.container):
compatible_type=user
name=arguments.string(lambda self:self.input.name)
is_admin=arguments.boolean(lambda self:self.input.is_admin)
age=arguments.value(lambda self:self.input.age)
这里有一些事情,让我们来分解一下它们的含义。
1。"userargument"类是"container"的子类。子类化是必需的,因为"container"实现了一些必需的api。
2。该类有一组类变量,这些变量是对"arguments.type"的调用,其中"type"是此参数的变量类型。目前有三种类型:一般值的"value",布尔值的"boolean",字符串值的"string"。
3.`` arguments.type()``是用返回值的可调用函数调用的。在上面的示例中,我们将根据用户的"name"、"is-admin"、"status"和"age"激活一些开关。
4。这些"参数"返回实际值,该值从"self.input"中取消限制,后者是输入对象(在本例中是"user"实例)。
5。``变量``objects理解``switch``条件和运算符,并实现正确的api,以便对它们进行适当的比较。
6.`` compatible_type``声明此参数仅适用于``user``实例。这与基本参数中的默认实现"applicates"一起工作,该基本参数检查输入的"type"是否与"compatible"类型相同。
因为构造简单引用"self.input"上的属性的参数非常常见,如果将字符串作为"argument()"的第一个参数传递,则在访问该参数时,它只会从"self.input"返回该属性。您还必须将"variable"传递给"variable=``kwarg",这样gutter就知道要用什么变量包装您的值了。
。代码::python
from gutter.client import arguments
from myapp import user
class userarguments(container):
compatible_type=user
name=arguments.string('name')
is_admin=arguments.boolean('name')
age=arguments.value('name')
arguments的基本原理
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
它们似乎只是在我的系统中包装一个对象并提供相同的api。为什么我不能使用我的业务对象**本身**,并将其与我的切换条件进行比较?
简短的回答是,``argument``对象提供了一个转换层,将业务对象转换成``gutter``理解的对象。这很重要,原因有两个。
在您的业务逻辑/对象中添加支持"gutter"的代码。您将要提供的所有参数声明给一个位置(参数)中的交换机,该位置的唯一职责是与"gutter"接口。您还可以构造更精明的参数对象,这些对象可以是多个业务对象的组合、咨询第三方服务等。所有这些仍然不会弄乱您的主要应用程序代码或业务对象。
其次,最重要的是,参数返回"variable"对象,确保"gutter"条件正常工作。这主要与基于百分比的运算符有关,最好用一个示例来说明。
假设您有一个"user"类,其中有一个"is-vip"布尔字段。假设您只想为10%的VIP客户启用一项功能。要做到这一点,您需要编写一个条件,条件是"当用变量调用我时,有10%的时间我应该是真的。"这一行代码可能会执行如下操作:
……代码::python
return 0<;=(散列(变量)%100<;10
代码::python
>;>;哈希(真)
1
>;>;哈希(真)%100
1
这不是您想要的行为。
对于10%的百分比范围,您希望它在10%的输入中处于活动状态。因此,每个输入必须有一个唯一的散列值,正好是"boolean"变量提供的特性。每个"variable"都有已知的条件特征,而您的对象可能没有。
也就是说,您不一定要**使用"variable"对象。对于很明显的情况,例如"use.age"gt;某些"u值"`your``user``实例可以正常工作,但是为了安全起见,应该使用"variable``对象。使用``variable``对象还可以确保如果更新``gutter``任何新的``operator``类型,则添加的``variable``类型都可以正确地与``variable``一起工作。
"关闭"取决于输入。开关通过检查每个"条件"并查看是否适用于某个输入来确定其开/关状态。
代码::python
from gutter.client.models import switch
switch可以处于3种核心状态:"全局"、"禁用"和"选择性"。在"global"状态下,无论什么情况,都会为每个输入启用开关。`` disabled``开关对于任何输入都不是**disabled**,不管是什么。``选择性"开关根据其条件启用。
代码::python
switch=switch('new feature',state=switch.states.disabled)
另一个u switch=switch('new feature')
另一个u switch.state=switch.states.disabled
复合
~对于要为特定输入启用的开关,为true。如果"switch.compledded"设置为"true",则**所有**开关条件都必须为true才能启用:
switch=switch('require alll conditions',复合=真)
heriarchical switches
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
您可以使用特定的分层命名方案创建交换机。交换机名称空间除以冒号字符(":"),交换机的层次结构可以这样构造:
…代码::python
parent=switch('电影')
child1=switch('电影:星球大战')
child2=switch('电影:死亡之路')
孙子=switch('电影:星球大战:新希望')
"`child1`"开关是"`"movies"`"开关的子开关,因为它的开关名前缀是"`movies:`"。child1和child2都是父开关parent的子开关。而"孙"是"child1"开关的子级,但*不是"child2"开关的子级。默认情况下,每个开关都会使其"我处于活动状态吗?"决策独立于管理器中的其他交换机(包括其父交换机),并且只参考其自身的条件来检查是否为输入启用了它。然而,情况并非总是如此。也许你有一个很酷的新功能,它只对某一类用户可用。在这些*用户中,您希望10%的用户暴露在不同的用户界面中,以查看他们与其他90%的用户相比的行为。
``gutter``允许您在交换机上设置一个``concent``标志,指示它在检查自身之前先检查其父交换机。如果它检查其父项,但没有为同一输入启用,则开关将立即返回"false"。如果其父*对输入启用*,则开关将继续并检查其自身条件,并按正常方式返回。
例如:
。代码::python
parent=switch('cool_new_feature')
child=switch('cool_new_feature:new_ui',concent=true)
只有当"parent"也为相同的输入启用**时,它才会返回"true"。
**注意:**即使处于"global"或"disabled"状态的开关(见上面的"switch"部分)在检查它们自己之前仍然同意它们的父级。这意味着,即使某个开关是"global",如果它的"concent"设置为"true",并且它的父级对输入启用**而不是**,开关本身将返回"false"。
否则,它将只存在于当前进程的内存中。通过"manager"实例上的"register"方法注册交换机:
…代码::python
gutter.register(switch)
开关现在存储在管理器的存储中,如果通过gutter.active(switch)`,则可以检查开关是否处于活动状态。
只需对"switch"对象进行更改,然后用开关调用"manager"的"update()"方法,告诉它用新对象更新开关:
。代码::python
switch=switch('cool switch')
manager.register(switch)
switch.name='even cooler switch'switch尚未在manager中更新
manager.update(switch)switch现在在manager中更新
模式(从管理器检索开关,然后更新它),Gutter提供了一个速记API,在该API中,您可以按名称向管理器请求开关,然后在**开关**上调用``save()``以在``manager``中更新它,它是从:
代码::python
switch=manager.switch('existing switch')
switch.name='a new name'switch尚未在管理器中更新
switch.save()与调用管理器相同。update(switch)
通过使用开关名或开关实例调用"unregister()"从管理器中删除:
…代码::python
gutter.unregister('deprecated switch')
gutter.unregister(a_switch_instance)
**注意:**如果交换机是层次结构的一部分并且有子交换机(参见上面的"分层交换机"部分),所有子交换机(子交换机、孙子交换机等)也将被注销和删除。
conditions
==
它描述了开关激活的条件。`` condition``对象由三个值构成:a``argument``,``attribute``和``operator``。
`argument``是任何``argument``类,与前面定义的类类似。在前面的例子中,``userargument``是一个参数对象。`` attribute``是要检查此条件的参数实例的属性。``运算符``是对该属性应用的某种检查。例如,"userargument.age"是否大于某个值?等于某个值?在一定范围内?等等。
假设您想要一个"条件",用于检查用户的年龄是否为65岁?你可以这样构造一个条件:
…代码::python
operator=more(65))
如果任何输入实例的"age"大于"65",则此条件为真。
这就否定了这个条件。例如:
…代码::python
在这种情况下,如果用户的"年龄"是**不**大于"65"。
代码::python
switch.conditions.append(condition)
为此,您可以用一个"user"实例调用开关的"enabled"for()``方法。您可以对任何输入对象调用"enabled for()",它将忽略对其一无所知的输入。如果输入的"switch"处于活动状态,则"enabled"将返回"true"。否则,它将返回``false`.
``gutter.active()``API
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
gutter的一个常见用例是在处理Web请求时使用它。在代码执行期间,根据某些交换机是否处于活动状态,将采用不同的代码路径。通常情况下,一次存在多个开关,需要根据多个参数检查它们。为了处理这个用例,gutter提供了一个更高级的api。
要检查"switch"是否处于活动状态,只需使用开关名调用"gutter.active()":
。代码::python
gutter.active("My Cool Feature")
>;>true
根据一些输入对象检查开关。输入可以添加到"active()"检查中,方法有两种:本地、传入到"active()"调用或全局,提前配置。
要检查本地输入,"`active()"在开关名称后使用任意数量的输入对象来检查开关。在本例中,将根据输入对象"input1"和"input2"检查名为"My Cool Feature"的开关:
。代码::python
gutter.active('My Cool Feature',input1,input2)
>;>true
代码::python
gutter.input(input1,input2)
现在,对每个"active"调用检查"input1"和"input2"。例如,假设"input1"和"input2"按上述方式配置,则此"active()"调用将按以下顺序检查是否为输入"input1"、"input2"和"input3"启用了开关::
gutter.active("My Cool Feature",输入3)
使用全局输入后,可能在请求结束时,应该调用管理器的"flush()"方法来删除所有输入:
。代码::python
gutter.flush()
您可以跳过检查全局输入的"switch"和**只检查本地传入的输入,方法是将"exclusive=true"作为关键字参数传递给"active()":
。代码::python
gutter.input(input1,input2)
gutter.active('My Cool Feature',input3,exclusive=true)
在上面的示例中,由于传递了'exclusive=true',名为'My Cool Feature'``的开关**只与'input3'进行了**检查,而不是"input1"或"input2"。"`exclusive=true`"参数不是持久的,因此下一次调用``active()``而不调用``exclusive=true``时将再次使用全局定义的输入。
signals
==
gutter提供4个连接到的总信号:3关于开关的更改,以及1个关于应用条件的错误。它们都可以从"gutter.signals"模块获得
switch signals
~`` switch_registered`—在向管理器注册新交换机时调用。
2。`` switch_unregistered`—在管理器中注销交换机时调用。
3。`` switch-updated``-使用switch调用的数据已更新。
若要使用信号,只需调用信号的``connect()``方法并传入一个可调用对象。当信号被触发时,它将使用正在注册/未注册/更新的开关调用您的callable。即:
…代码::python
from gutter.client.signals import switch_updated
def log_switch_update(switch):
syslog.log("switch%s updated%switch.name)
switch_updated.connect(log_switch_updated)
更改
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
可以连接到"switch\u updated"信号,以便在更改开关时收到通知。要知道交换机中的*什么*发生了更改,可以查看其"更改"属性:
…代码::python
>;>;来自gutter.client.models导入开关
>;>;switch=switch('test')
>;>;switch.concent
>;true
>;>;switch.concent=false
>;>;switch.name='new name'
>;>;switch.changes
{concent':{current':false,'previous':true},'name':{current':'new name,'previous':'test'}
您还可以简单地使用"changed"属性询问开关是否有任何更改。如果交换机有任何更改,则返回"true"或"false"。
即,仅当更改包含已更改的条件时,才通过电子邮件发送diff。
"argument"值很可能是某种意外值,并可能导致异常。当"condition"根据输入检查自身时出现异常时,"condition"将捕获该异常并返回"fals"e``.
捕获所有异常通常是错误的形式并隐藏错误,但大多数情况下,您不希望仅仅因为检查交换机条件时出错而使应用程序请求失败,*尤其是*如果在检查"条件"时出现错误,而用户本来不会对其应用该错误。
为了实现这一点,``gutter`-客户机提供了一个``condition_apply_error``信号,在检查``condition``时调用该信号。该信号是用条件的实例、导致错误的输入和异常类本身的实例调用的:
。代码::python
signals.condition_apply_error.call(condition,inpt,error)
等等,
namespaces
==
``gutter``允许使用"namespaces"在单个保护伞下对交换机进行分组,同时不允许一个命名空间看到另一个命名空间的交换机,但允许它们共享同一个存储实例,运算符和其他配置。
给定一个现有的vanilla ``manager``实例,可以通过调用``namespaced()``方法来创建名称空间管理器:
。代码::python
notifications=gutter.namespaced('notifications')
此时,``notifications``是``gutter``的副本,继承了它的所有:
*存储
*``autocreate``设置
*全局输入
*运算符
共享相同的开关。新构造的"manager"实例位于"default"命名空间中。调用"namespaced()"时,"gutter"会将管理器的命名空间更改为"notifications"。前一个"default"名称空间中的任何开关在"notifications"名称空间中都不可见,反之亦然。
不让它们相互冲突。
decorators
==
gutter提供了一个"switch\u active"decorator,可以用来装饰django视图。修饰时,如果名为"switch"修饰符的第一个参数的开关为false,则会引发"http404"异常。但是,如果您还将"redirect"传递给``kwarg,那么decorator将返回一个``httpresponseredirect`实例,并重定向到该位置。如果开关处于活动状态,则视图将正常运行。
例如,这里有一个用"`@switch_active`"修饰的视图:
。代码::python
from gutter.client.decorators import switch_active
@switch_active('cool_feature')
def my_view(request):
return'foo'
此视图将引发"http404"异常。
代码::python
@switch_active('cool_feature',redirect_to=reverse('upsell-page')
然后返回一个"httpresponseredirect"实例,重定向到"reverse('upsell-page')`"。
testing utilities
==
您可以使用"testutils"模块中的"switches"对象。
可以将"swtiches"对象用作上下文管理器和装饰器。它被传递开关名的"kwargs"及其"active"返回值。
使用其他交换机名称调用"active()"将返回其实际的实时交换机状态:
…代码:python
from gutter.client.testutils import switches
from gutter.client.default import gutter
代码::python
from gutter.client.testutils import switches
from gutter.client.default import gutter
@switches(cool_feature=true)
def run(self):
gutter.active('cool_feature')\true
另外,您可以将备用的"manager"实例传递给"switches",以使用该管理器而不是默认的管理器:
。代码::python
from gutter.client.testutils导入开关
from gutter.client.models导入管理器
@switches(mymanager,cool feature=true)
def run(self):
gutter.active("coolfeature")true
:目标:http://travis ci.org/disks/gutter
gutter
----
**注意:**此回购是Gargoyle 2的客户端,称为"gutter"。它不能与现有的"gargoyle 1代码库"一起使用,https://github.com/disks/gargoyle/>;` `.
gutter是功能切换管理库。它允许用户创建功能开关和这些开关将启用的设置条件。配置后,可以根据输入(请求、用户对象等)检查开关,以查看开关是否处于活动状态。
有关使用"Gutter django项目"配置Gutter的UI,请参阅https://github.com/disfs/Gutter django>;`
实用程序`
配置
==
Gutter在使用前需要进行少量配置。
它是一个"dict"或任何提供"types.mappingtype"接口的对象(```````setitem``和```getitem``方法)。默认情况下,``gutter``使用来自'durabledict library<;https://github.com/disks/durabledict>;``的'memorydict'实例。此引擎**不会在进程结束后保留数据**,因此应使用更持久的数据存储。
``gutter``还可以"autocreate"开关。如果启用了"autocreate",并且询问"gutter"开关是否处于活动状态但尚未创建开关,则"gutter"将自动创建开关。自动创建时,开关的状态设置为"禁用"。
有关下面"设置"的更多信息。
配置设置
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>若要更改"存储"和/或"自动创建"设置,只需导入设置模块并设置相应的变量:
。代码::python
redisclient())
manager_settings.autocreate=true
在本例中,我们将引擎更改为durabledict的"redisdict",并打开"autocreate"。然后,这些设置将应用于所有新构建的"manager"实例。关于"manager"是什么以及稍后在本文中如何使用它的更多信息。
setup
=
一旦配置了"manager"的存储引擎,就可以导入gutter的默认"manager"对象,它是您与"gutter"的主界面:
。code::python
from gutter.client.default import gutter
此时,``gutter`对象是``manager``类的一个实例,该类保存所有注册开关的方法,并检查它们是否处于活动状态。在大多数安装和使用场景中,"gutter.client.gutter"管理器将是您的主界面。
`` gutter.client.gutter``,您可以构造一个``manager``实例,然后将其分配给``settings.manager.default``值:
。代码::python
from gutter.client.settings导入manager as manager_settings
from gutter.client.models导入manager
manager_settings.default=manager({})必须在从gutter.client.default导入默认管理器之前完成槽
assert manager\u settings.默认值为gutter
。警告:
:警告::警告:
请注意,在**导入默认"gutter"实例之前,必须设置"settings.manager.default"值**。
:警告::警告:
参数
==
您将要检查的参数。"参数"是一个对象,它了解系统中的业务逻辑和对象(用户、请求等),并知道如何验证、转换和提取这些业务对象中的变量,以满足"切换"条件。例如,您的系统可能有一个"user"对象,该对象具有诸如"is-admin"、"date-joined"等属性。若要与之进行切换,则需要为每个值创建参数。
若要执行此操作,请构造一个继承自"gutter.client.arguments.container"的类。在类的主体中,通过使用"gutter.client.arguments"函数可以创建所需的类变量"arguments"。
代码::python
from gutter.client import arguments
from myapp import user
class userarguments(arguments.container):
compatible_type=user
name=arguments.string(lambda self:self.input.name)
is_admin=arguments.boolean(lambda self:self.input.is_admin)
age=arguments.value(lambda self:self.input.age)
这里有一些事情,让我们来分解一下它们的含义。
1。"userargument"类是"container"的子类。子类化是必需的,因为"container"实现了一些必需的api。
2。该类有一组类变量,这些变量是对"arguments.type"的调用,其中"type"是此参数的变量类型。目前有三种类型:一般值的"value",布尔值的"boolean",字符串值的"string"。
3.`` arguments.type()``是用返回值的可调用函数调用的。在上面的示例中,我们将根据用户的"name"、"is-admin"、"status"和"age"激活一些开关。
4。这些"参数"返回实际值,该值从"self.input"中取消限制,后者是输入对象(在本例中是"user"实例)。
5。``变量``objects理解``switch``条件和运算符,并实现正确的api,以便对它们进行适当的比较。
6.`` compatible_type``声明此参数仅适用于``user``实例。这与基本参数中的默认实现"applicates"一起工作,该基本参数检查输入的"type"是否与"compatible"类型相同。
因为构造简单引用"self.input"上的属性的参数非常常见,如果将字符串作为"argument()"的第一个参数传递,则在访问该参数时,它只会从"self.input"返回该属性。您还必须将"variable"传递给"variable=``kwarg",这样gutter就知道要用什么变量包装您的值了。
。代码::python
from gutter.client import arguments
from myapp import user
class userarguments(container):
compatible_type=user
name=arguments.string('name')
is_admin=arguments.boolean('name')
age=arguments.value('name')
arguments的基本原理
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
它们似乎只是在我的系统中包装一个对象并提供相同的api。为什么我不能使用我的业务对象**本身**,并将其与我的切换条件进行比较?
简短的回答是,``argument``对象提供了一个转换层,将业务对象转换成``gutter``理解的对象。这很重要,原因有两个。
在您的业务逻辑/对象中添加支持"gutter"的代码。您将要提供的所有参数声明给一个位置(参数)中的交换机,该位置的唯一职责是与"gutter"接口。您还可以构造更精明的参数对象,这些对象可以是多个业务对象的组合、咨询第三方服务等。所有这些仍然不会弄乱您的主要应用程序代码或业务对象。
其次,最重要的是,参数返回"variable"对象,确保"gutter"条件正常工作。这主要与基于百分比的运算符有关,最好用一个示例来说明。
假设您有一个"user"类,其中有一个"is-vip"布尔字段。假设您只想为10%的VIP客户启用一项功能。要做到这一点,您需要编写一个条件,条件是"当用变量调用我时,有10%的时间我应该是真的。"这一行代码可能会执行如下操作:
……代码::python
return 0<;=(散列(变量)%100<;10
代码::python
>;>;哈希(真)
1
>;>;哈希(真)%100
1
这不是您想要的行为。
对于10%的百分比范围,您希望它在10%的输入中处于活动状态。因此,每个输入必须有一个唯一的散列值,正好是"boolean"变量提供的特性。每个"variable"都有已知的条件特征,而您的对象可能没有。
也就是说,您不一定要**使用"variable"对象。对于很明显的情况,例如"use.age"gt;某些"u值"`your``user``实例可以正常工作,但是为了安全起见,应该使用"variable``对象。使用``variable``对象还可以确保如果更新``gutter``任何新的``operator``类型,则添加的``variable``类型都可以正确地与``variable``一起工作。
"关闭"取决于输入。开关通过检查每个"条件"并查看是否适用于某个输入来确定其开/关状态。
代码::python
from gutter.client.models import switch
switch可以处于3种核心状态:"全局"、"禁用"和"选择性"。在"global"状态下,无论什么情况,都会为每个输入启用开关。`` disabled``开关对于任何输入都不是**disabled**,不管是什么。``选择性"开关根据其条件启用。
代码::python
switch=switch('new feature',state=switch.states.disabled)
另一个u switch=switch('new feature')
另一个u switch.state=switch.states.disabled
复合
~对于要为特定输入启用的开关,为true。如果"switch.compledded"设置为"true",则**所有**开关条件都必须为true才能启用:
switch=switch('require alll conditions',复合=真)
heriarchical switches
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
您可以使用特定的分层命名方案创建交换机。交换机名称空间除以冒号字符(":"),交换机的层次结构可以这样构造:
…代码::python
parent=switch('电影')
child1=switch('电影:星球大战')
child2=switch('电影:死亡之路')
孙子=switch('电影:星球大战:新希望')
"`child1`"开关是"`"movies"`"开关的子开关,因为它的开关名前缀是"`movies:`"。child1和child2都是父开关parent的子开关。而"孙"是"child1"开关的子级,但*不是"child2"开关的子级。默认情况下,每个开关都会使其"我处于活动状态吗?"决策独立于管理器中的其他交换机(包括其父交换机),并且只参考其自身的条件来检查是否为输入启用了它。然而,情况并非总是如此。也许你有一个很酷的新功能,它只对某一类用户可用。在这些*用户中,您希望10%的用户暴露在不同的用户界面中,以查看他们与其他90%的用户相比的行为。
``gutter``允许您在交换机上设置一个``concent``标志,指示它在检查自身之前先检查其父交换机。如果它检查其父项,但没有为同一输入启用,则开关将立即返回"false"。如果其父*对输入启用*,则开关将继续并检查其自身条件,并按正常方式返回。
例如:
。代码::python
parent=switch('cool_new_feature')
child=switch('cool_new_feature:new_ui',concent=true)
只有当"parent"也为相同的输入启用**时,它才会返回"true"。
**注意:**即使处于"global"或"disabled"状态的开关(见上面的"switch"部分)在检查它们自己之前仍然同意它们的父级。这意味着,即使某个开关是"global",如果它的"concent"设置为"true",并且它的父级对输入启用**而不是**,开关本身将返回"false"。
否则,它将只存在于当前进程的内存中。通过"manager"实例上的"register"方法注册交换机:
…代码::python
gutter.register(switch)
开关现在存储在管理器的存储中,如果通过gutter.active(switch)`,则可以检查开关是否处于活动状态。
只需对"switch"对象进行更改,然后用开关调用"manager"的"update()"方法,告诉它用新对象更新开关:
。代码::python
switch=switch('cool switch')
manager.register(switch)
switch.name='even cooler switch'switch尚未在manager中更新
manager.update(switch)switch现在在manager中更新
模式(从管理器检索开关,然后更新它),Gutter提供了一个速记API,在该API中,您可以按名称向管理器请求开关,然后在**开关**上调用``save()``以在``manager``中更新它,它是从:
代码::python
switch=manager.switch('existing switch')
switch.name='a new name'switch尚未在管理器中更新
switch.save()与调用管理器相同。update(switch)
通过使用开关名或开关实例调用"unregister()"从管理器中删除:
…代码::python
gutter.unregister('deprecated switch')
gutter.unregister(a_switch_instance)
**注意:**如果交换机是层次结构的一部分并且有子交换机(参见上面的"分层交换机"部分),所有子交换机(子交换机、孙子交换机等)也将被注销和删除。
conditions
==
它描述了开关激活的条件。`` condition``对象由三个值构成:a``argument``,``attribute``和``operator``。
`argument``是任何``argument``类,与前面定义的类类似。在前面的例子中,``userargument``是一个参数对象。`` attribute``是要检查此条件的参数实例的属性。``运算符``是对该属性应用的某种检查。例如,"userargument.age"是否大于某个值?等于某个值?在一定范围内?等等。
假设您想要一个"条件",用于检查用户的年龄是否为65岁?你可以这样构造一个条件:
…代码::python
operator=more(65))
如果任何输入实例的"age"大于"65",则此条件为真。
这就否定了这个条件。例如:
…代码::python
在这种情况下,如果用户的"年龄"是**不**大于"65"。
代码::python
switch.conditions.append(condition)
为此,您可以用一个"user"实例调用开关的"enabled"for()``方法。您可以对任何输入对象调用"enabled for()",它将忽略对其一无所知的输入。如果输入的"switch"处于活动状态,则"enabled"将返回"true"。否则,它将返回``false`.
``gutter.active()``API
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
gutter的一个常见用例是在处理Web请求时使用它。在代码执行期间,根据某些交换机是否处于活动状态,将采用不同的代码路径。通常情况下,一次存在多个开关,需要根据多个参数检查它们。为了处理这个用例,gutter提供了一个更高级的api。
要检查"switch"是否处于活动状态,只需使用开关名调用"gutter.active()":
。代码::python
gutter.active("My Cool Feature")
>;>true
根据一些输入对象检查开关。输入可以添加到"active()"检查中,方法有两种:本地、传入到"active()"调用或全局,提前配置。
要检查本地输入,"`active()"在开关名称后使用任意数量的输入对象来检查开关。在本例中,将根据输入对象"input1"和"input2"检查名为"My Cool Feature"的开关:
。代码::python
gutter.active('My Cool Feature',input1,input2)
>;>true
代码::python
gutter.input(input1,input2)
现在,对每个"active"调用检查"input1"和"input2"。例如,假设"input1"和"input2"按上述方式配置,则此"active()"调用将按以下顺序检查是否为输入"input1"、"input2"和"input3"启用了开关::
gutter.active("My Cool Feature",输入3)
使用全局输入后,可能在请求结束时,应该调用管理器的"flush()"方法来删除所有输入:
。代码::python
gutter.flush()
您可以跳过检查全局输入的"switch"和**只检查本地传入的输入,方法是将"exclusive=true"作为关键字参数传递给"active()":
。代码::python
gutter.input(input1,input2)
gutter.active('My Cool Feature',input3,exclusive=true)
在上面的示例中,由于传递了'exclusive=true',名为'My Cool Feature'``的开关**只与'input3'进行了**检查,而不是"input1"或"input2"。"`exclusive=true`"参数不是持久的,因此下一次调用``active()``而不调用``exclusive=true``时将再次使用全局定义的输入。
signals
==
gutter提供4个连接到的总信号:3关于开关的更改,以及1个关于应用条件的错误。它们都可以从"gutter.signals"模块获得
switch signals
~`` switch_registered`—在向管理器注册新交换机时调用。
2。`` switch_unregistered`—在管理器中注销交换机时调用。
3。`` switch-updated``-使用switch调用的数据已更新。
若要使用信号,只需调用信号的``connect()``方法并传入一个可调用对象。当信号被触发时,它将使用正在注册/未注册/更新的开关调用您的callable。即:
…代码::python
from gutter.client.signals import switch_updated
def log_switch_update(switch):
syslog.log("switch%s updated%switch.name)
switch_updated.connect(log_switch_updated)
更改
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
可以连接到"switch\u updated"信号,以便在更改开关时收到通知。要知道交换机中的*什么*发生了更改,可以查看其"更改"属性:
…代码::python
>;>;来自gutter.client.models导入开关
>;>;switch=switch('test')
>;>;switch.concent
>;true
>;>;switch.concent=false
>;>;switch.name='new name'
>;>;switch.changes
{concent':{current':false,'previous':true},'name':{current':'new name,'previous':'test'}
您还可以简单地使用"changed"属性询问开关是否有任何更改。如果交换机有任何更改,则返回"true"或"false"。
即,仅当更改包含已更改的条件时,才通过电子邮件发送diff。
"argument"值很可能是某种意外值,并可能导致异常。当"condition"根据输入检查自身时出现异常时,"condition"将捕获该异常并返回"fals"e``.
捕获所有异常通常是错误的形式并隐藏错误,但大多数情况下,您不希望仅仅因为检查交换机条件时出错而使应用程序请求失败,*尤其是*如果在检查"条件"时出现错误,而用户本来不会对其应用该错误。
为了实现这一点,``gutter`-客户机提供了一个``condition_apply_error``信号,在检查``condition``时调用该信号。该信号是用条件的实例、导致错误的输入和异常类本身的实例调用的:
。代码::python
signals.condition_apply_error.call(condition,inpt,error)
等等,
namespaces
==
``gutter``允许使用"namespaces"在单个保护伞下对交换机进行分组,同时不允许一个命名空间看到另一个命名空间的交换机,但允许它们共享同一个存储实例,运算符和其他配置。
给定一个现有的vanilla ``manager``实例,可以通过调用``namespaced()``方法来创建名称空间管理器:
。代码::python
notifications=gutter.namespaced('notifications')
此时,``notifications``是``gutter``的副本,继承了它的所有:
*存储
*``autocreate``设置
*全局输入
*运算符
共享相同的开关。新构造的"manager"实例位于"default"命名空间中。调用"namespaced()"时,"gutter"会将管理器的命名空间更改为"notifications"。前一个"default"名称空间中的任何开关在"notifications"名称空间中都不可见,反之亦然。
不让它们相互冲突。
decorators
==
gutter提供了一个"switch\u active"decorator,可以用来装饰django视图。修饰时,如果名为"switch"修饰符的第一个参数的开关为false,则会引发"http404"异常。但是,如果您还将"redirect"传递给``kwarg,那么decorator将返回一个``httpresponseredirect`实例,并重定向到该位置。如果开关处于活动状态,则视图将正常运行。
例如,这里有一个用"`@switch_active`"修饰的视图:
。代码::python
from gutter.client.decorators import switch_active
@switch_active('cool_feature')
def my_view(request):
return'foo'
此视图将引发"http404"异常。
代码::python
@switch_active('cool_feature',redirect_to=reverse('upsell-page')
然后返回一个"httpresponseredirect"实例,重定向到"reverse('upsell-page')`"。
testing utilities
==
您可以使用"testutils"模块中的"switches"对象。
可以将"swtiches"对象用作上下文管理器和装饰器。它被传递开关名的"kwargs"及其"active"返回值。
使用其他交换机名称调用"active()"将返回其实际的实时交换机状态:
…代码:python
from gutter.client.testutils import switches
from gutter.client.default import gutter
代码::python
from gutter.client.testutils import switches
from gutter.client.default import gutter
@switches(cool_feature=true)
def run(self):
gutter.active('cool_feature')\true
另外,您可以将备用的"manager"实例传递给"switches",以使用该管理器而不是默认的管理器:
。代码::python
from gutter.client.testutils导入开关
from gutter.client.models导入管理器
@switches(mymanager,cool feature=true)
def run(self):
gutter.active("coolfeature")true