客户端到边沟功能交换机后端

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

欢迎加入QQ群-->: 979659372 Python中文网_新手群

推荐PyPI第三方库


热门话题
java读取SSLSocket的最快或最佳方式   JavaGuice向类构造函数注入对象   java类不是抽象的,并且不会覆盖OnClickListener中的抽象方法onClick(视图)   java OpenGL 1.1更改颜色更改以前的颜色?   c#将Java/Android连接到。网络服务   java在节点上生成AES密钥   java Liferay与MarkLogic XDBC数据库集成   java使用静态初始化块来提高性能   java如何在需要不同参数的另一个方法中使用同一类中的方法   音频Java多种声音   Java显式引用转换   java Intellij,如何在maven项目中导入模块   java在什么条件下调用ELResolver的setValue方法?   java在beanshell中计算代码字符串并获取beanshell解释器返回的值   javascript将音频文件上载到服务器并从服务器响应设置图像   编码风格清理java代码,多个else if语句   java是否需要使此变量可变?   java线程未更新GlassPane上的进度条   java关闭调试模式@Vaadin Spring启动应用程序