Zope3中单个对象的可自定义日志。
zc.objectlog的Python项目详细描述
object log包为单个对象提供可自定义的日志。这个 该系统旨在为 对象更改并为度量提供可分析的信息。
- 它为时间戳和 日志生成时请求中的主体。
- 给定要收集的有关对象的数据架构,它将自动 计算并存储从最后一个日志项到 对“什么改变了”这个问题提供一个快速而简单的答案。“和 其次是减少数据库的大小。
- 它接受允许系统或用户使用的可选摘要和详细信息值 用人类可读的消息注释条目。
- 它允许用零个或多个标记接口对每个日志条目进行注释 以便可以使用接口对日志项进行分类。
此外,可以将日志项设置为发生在转换边界处,并且 仅当自上次更改以来(根据更改集)进行了更改时 日志条目。
为了说明这一点,我们需要建立一个虚拟交互。我们在下面做,然后 用日志创建一个对象,然后实际创建一个日志。
>>> import zope.security.management >>> import zope.security.interfaces >>> import zope.app.security.interfaces >>> from zope import interface, schema >>> from zope.app.testing import ztapi >>> class DummyPrincipal(object): ... interface.implements(zope.security.interfaces.IPrincipal) ... def __init__(self, id, title, description): ... self.id = unicode(id) ... self.title = title ... self.description = description ... >>> alice = DummyPrincipal('alice', 'Alice Aal', 'first principal') >>> betty = DummyPrincipal('betty', 'Betty Barnes', 'second principal') >>> cathy = DummyPrincipal('cathy', 'Cathy Camero', 'third principal') >>> class DummyParticipation(object): ... interface.implements(zope.security.interfaces.IParticipation) ... interaction = principal = None ... def __init__(self, principal): ... self.principal = principal ... >>> import zope.publisher.interfaces>>> import zc.objectlog >>> import zope.location >>> WORKING = u"Where I'm working" >>> COUCH = u"On couch" >>> BED = u"On bed" >>> KITCHEN = u"In kitchen" >>> class ICat(interface.Interface): ... name = schema.TextLine(title=u"Name", required=True) ... location = schema.Choice( ... (WORKING, COUCH, BED, KITCHEN), ... title=u"Location", required=False) ... weight = schema.Int(title=u"Weight in Pounds", required=True) ... getAge, = schema.accessors( ... schema.Int(title=u"Age in Years", readonly=True, ... required=False)) ... >>> import persistent >>> class Cat(persistent.Persistent): ... interface.implements(ICat) ... def __init__(self, name, weight, age, location=None): ... self.name = name ... self.weight = weight ... self.location = location ... self._age = age ... self.log = zc.objectlog.Log(ICat) ... zope.location.locate(self.log, self, 'log') ... def getAge(self): ... return self._age ...
注意,在猫的初始化中,我们找到了猫的日志。这是 一个重要的步骤,因为它启用了自动变更集。
现在我们来看看例子。除了一个例外,每个例子 在人造交互中运行,以便我们可以看到主体如何识别 属性有效。首先我们将看到len工作,记录模式 属性设置正确,时间戳使用pytz.utc时区 时间戳、日志迭代、摘要、详细信息和数据 设置正确。
>>> import pytz, datetime >>> a_p = DummyParticipation(alice) >>> interface.directlyProvides(a_p, zope.publisher.interfaces.IRequest) >>> zope.security.management.newInteraction(a_p) >>> emily = Cat(u'Emily', 16, 5, WORKING) >>> len(emily.log) 0 >>> emily.log.record_schema is ICat True >>> before = datetime.datetime.now(pytz.utc) >>> entry = emily.log( ... u'Starting to keep track of Emily', ... u'Looks like\nshe might go upstairs soon') >>> entry is emily.log[0] True >>> after = datetime.datetime.now(pytz.utc) >>> len(emily.log) 1 >>> before <= entry.timestamp <= after True >>> entry.timestamp.tzinfo is pytz.utc True >>> entry.principal_ids (u'alice',) >>> list(emily.log) == [entry] True >>> entry.record_schema is ICat True >>> entry.summary u'Starting to keep track of Emily' >>> entry.details u'Looks like\nshe might go upstairs soon'
记录和记录更改应具有 反对。该记录有一个特殊的安全检查程序,允许用户 访问架构上定义的任何字段,但不访问任何其他字段,也不访问 写下任何值。
>>> record = emily.log[0].record >>> record.name u'Emily' >>> record.location==WORKING True >>> record.weight 16 >>> record.getAge() 5 >>> ICat.providedBy(record) True >>> emily.log[0].record_changes == { ... 'name': u'Emily', 'weight': 16, 'location': u"Where I'm working", ... 'getAge': 5} True >>> from zope.security.checker import ProxyFactory >>> proxrecord = ProxyFactory(record) >>> ICat.providedBy(proxrecord) True >>> from zc.objectlog import interfaces >>> interfaces.IRecord.providedBy(proxrecord) True >>> from zope.security import canAccess, canWrite >>> canAccess(record, 'name') True >>> canAccess(record, 'weight') True >>> canAccess(record, 'location') True >>> canAccess(record, 'getAge') True >>> canAccess(record, 'shazbot') # doctest: +ELLIPSIS Traceback (most recent call last): ... ForbiddenAttribute: ('shazbot', ... >>> canWrite(record, 'name') False >>> zope.security.management.endInteraction()
与多个主体的交互也被正确记录。注意 非请求参与不包括在记录中。我们也 再看一下记录和变更集。
>>> a_p = DummyParticipation(alice) >>> b_p = DummyParticipation(betty) >>> c_p = DummyParticipation(cathy) >>> interface.directlyProvides(a_p, zope.publisher.interfaces.IRequest) >>> interface.directlyProvides(b_p, zope.publisher.interfaces.IRequest) >>> zope.security.management.newInteraction(a_p, b_p, c_p) >>> emily.location = KITCHEN >>> entry = emily.log(u"Sounds like she's eating", u"Dry food,\nin fact.") >>> len(emily.log) 2 >>> emily.log[0].summary u'Starting to keep track of Emily' >>> emily.log[1].summary u"Sounds like she's eating" >>> after <= emily.log[1].timestamp <= datetime.datetime.now(pytz.utc) True >>> emily.log[1].principal_ids # cathy was not a request, so not included (u'alice', u'betty') >>> emily.log[1].details u'Dry food,\nin fact.' >>> emily.log[1].record_changes {'location': u'In kitchen'} >>> record = emily.log[1].record >>> record.location u'In kitchen' >>> record.name u'Emily' >>> record.weight 16 >>> zope.security.management.endInteraction()
也可以在没有交互的情况下生成日志。
>>> emily._age = 6 >>> entry = emily.log(u'Happy Birthday') # no interaction >>> len(emily.log) 3 >>> emily.log[2].principal_ids () >>> emily.log[2].record_changes {'getAge': 6} >>> record = emily.log[2].record >>> record.location u'In kitchen' >>> record.name u'Emily' >>> record.weight 16 >>> record.getAge() 6
条目可以用标记接口标记以对其进行分类。这种方法 安全代理可能很困难,因此可能会更改。我们会尽力的 我们在同一交互中的其他示例。
>>> c_p = DummyParticipation(cathy) >>> interface.directlyProvides(c_p, zope.publisher.interfaces.IRequest) >>> zope.security.management.newInteraction(c_p) >>> emily.location = None >>> emily.weight = 17 >>> class IImportantLogEntry(interface.Interface): ... "A marker interface for log entries" >>> interface.directlyProvides( ... emily.log(u'Emily is in transit...and ate a bit too much'), ... IImportantLogEntry) >>> len(emily.log) 4 >>> [e for e in emily.log if IImportantLogEntry.providedBy(e)] == [ ... emily.log[3]] True >>> emily.log[3].principal_ids (u'cathy',) >>> emily.log[3].record_changes=={'weight': 17, 'location': None} True >>> record = emily.log[3].record >>> old_record = emily.log[2].record >>> record.name == old_record.name == u'Emily' True >>> record.weight 17 >>> old_record.weight 16 >>> record.location # None >>> old_record.location u'In kitchen'
如果试图生成的记录不一致,则生成日志将失败 到它的模式。
>>> emily.location = u'Outside' >>> emily.log(u'This should never happen') Traceback (most recent call last): ... ConstraintNotSatisfied: Outside >>> len(emily.log) 4 >>> emily.location = BED
如果传递给它的参数不正确,它也将失败。
>>> emily.log("This isn't unicode so will not succeed") Traceback (most recent call last): ... WrongType: ("This isn't unicode so will not succeed", <type 'unicode'>) >>> len(emily.log) 4 >>> success = emily.log(u"Yay, unicode")
在我们有更多信息之前,以下内容将被注释掉
# >>> emily.log(u”Data without an interface won’t work”, None, ‘boo hoo’) Traceback (most recent call last): … WrongContainedType: []
日志项中可能包含零个或多个其他任意数据对象 只要它们实现一个接口。
>>> class IConsumableRecord(interface.Interface): ... dry_food = schema.Int( ... title=u"Dry found consumed in ounces", required=False) ... wet_food = schema.Int( ... title=u"Wet food consumed in ounces", required=False) ... water = schema.Int( ... title=u"Water consumed in teaspoons", required=False) ...# >>> class ConsumableRecord(object): … interface.implements(IConsumableRecord) … def __init__(self, dry_food=None, wet_food=None, water=None): … self.dry_food = dry_food … self.wet_food = wet_food … self.water = water … # >>> entry = emily.log(u’Collected eating records’, None, ConsumableRecord(1)) # >>> len(emily.log) 5 # >>> len(emily.log[4].data) 1 # >>> IConsumableRecord.providedBy(emily.log[4].data[0]) True # >>> emily.log[4].data[0].dry_food 1
_ getitem和iter在python序列中正常工作,包括 支持扩展切片。
>>> list(emily.log) == [emily.log[0], emily.log[1], emily.log[2], ... emily.log[3], emily.log[4]] True >>> emily.log[-1] is emily.log[4] True >>> emily.log[0] is emily.log[-5] True >>> emily.log[5] Traceback (most recent call last): ... IndexError: list index out of range >>> emily.log[-6] Traceback (most recent call last): ... IndexError: list index out of range >>> emily.log[4:2:-1] == [emily.log[4], emily.log[3]] True
只要没有日志或 接口扩展(或是)上一个日志的接口。
>>> emily.log.record_schema = IConsumableRecord # doctest: +ELLIPSIS Traceback (most recent call last): ... ValueError: Once entries have been made, may only change schema to one... >>> class IExtendedCat(ICat): ... parent_object_intid = schema.Int(title=u"Parent Object") ... >>> emily.log.record_schema = IExtendedCat >>> emily.log.record_schema = ICat >>> emily.log.record_schema = IExtendedCat >>> class ExtendedCatAdapter(object): ... interface.implements(IExtendedCat) ... def __init__(self, cat): # getAge is left off ... self.name = cat.name ... self.weight = cat.weight ... self.location = cat.location ... self.parent_object_intid = 42 ... >>> ztapi.provideAdapter((ICat,), IExtendedCat, ExtendedCatAdapter) >>> entry = emily.log(u'First time with extended interface') >>> emily.log.record_schema = ICat # doctest: +ELLIPSIS Traceback (most recent call last): ... ValueError: Once entries have been made, may only change schema to one... >>> emily.log[5].record_changes == { ... 'parent_object_intid': 42, 'getAge': None} True >>> record = emily.log[5].record >>> record.parent_object_intid 42 >>> record.name u'Emily' >>> record.location u'On bed' >>> record.weight 17 >>> record.getAge() # None >>> IExtendedCat.providedBy(record) True >>> old_record = emily.log[3].record >>> IExtendedCat.providedBy(old_record) False >>> ICat.providedBy(old_record) True >>> old_record.parent_object_intid # doctest: +ELLIPSIS Traceback (most recent call last): ... AttributeError: ...
条目支持方便的next和previous属性,这使得它们 像不可变的双链表一样:
>>> entry = emily.log[5] >>> entry.previous is emily.log[4] True >>> entry.next # None >>> entry.previous.previous.previous.previous.previous is emily.log[0] True >>> emily.log[0].previous # None >>> emily.log[0].next is emily.log[1] True
objectlogs还支持延迟到事务结束。显示 我们需要一个示例数据库、一个事务和密钥引用 适配器。我们先举一个最简单的例子。
>>> from ZODB.tests import util >>> import transaction>>> db = util.DB() >>> connection = db.open() >>> root = connection.root() >>> root["emily"] = emily >>> transaction.commit() >>> import zope.app.keyreference.persistent >>> import zope.app.keyreference.interfaces >>> import ZODB.interfaces >>> import persistent.interfaces >>> from zope import component >>> component.provideAdapter( ... zope.app.keyreference.persistent.KeyReferenceToPersistent, ... (persistent.interfaces.IPersistent,), ... zope.app.keyreference.interfaces.IKeyReference) >>> component.provideAdapter( ... zope.app.keyreference.persistent.connectionOfPersistent, ... (persistent.interfaces.IPersistent,), ... ZODB.interfaces.IConnection)>>> len(emily.log) 6 >>> emily.log(u'This one is deferred', defer=True) # returns None: deferred! >>> len(emily.log) 6 >>> transaction.commit() >>> len(emily.log) 7 >>> emily.log[6].summary u'This one is deferred' >>> emily.log[6].record_changes {}
虽然这很有趣,但重点是捕捉对象的更改, 是否在调用日志时发生。这里有一个更中肯的 那就举个例子吧。
>>> len(emily.log) 7 >>> emily.weight = 16 >>> emily.log(u'Also deferred', defer=True) # returns None: deferred! >>> len(emily.log) 7 >>> emily.location = COUCH >>> transaction.commit() >>> len(emily.log) 8 >>> emily.log[7].summary u'Also deferred' >>> import pprint >>> pprint.pprint(emily.log[7].record_changes) {'location': u'On couch', 'weight': 16}
如果需要,可以延迟多个延迟日志项。
>>> emily.log(u'One log', defer=True) >>> emily.log(u'Two log', defer=True) >>> len(emily.log) 8 >>> transaction.commit() >>> len(emily.log) 10 >>> emily.log[8].summary u'One log' >>> emily.log[9].summary u'Two log'
另一个选择是如果你改变了。它不应该做日志,除非有 改变。
>>> len(emily.log) 10 >>> emily.log(u'If changed', if_changed=True) # returns None: no change! >>> len(emily.log) 10 >>> emily.location = BED >>> entry = emily.log(u'If changed', if_changed=True) >>> len(emily.log) 11 >>> emily.log[10] is entry True >>> entry.summary u'If changed' >>> pprint.pprint(entry.record_changes) {'location': u'On bed'} >>> transaction.commit()
两个选项,如果R,可以一起使用。这使得 一个日志项,如果有 以前没有变化。请注意,无论 进行了更改(以下称为“必需”日志项),也就是 如果更改了日志项,即使 稍后在事务中注册了所需的日志项。
>>> len(emily.log) 11 >>> emily.log(u'Another', defer=True, if_changed=True) # returns None >>> transaction.commit() >>> len(emily.log) 11 >>> emily.log(u'Yet another', defer=True, if_changed=True) # returns None >>> emily.location = COUCH >>> len(emily.log) 11 >>> transaction.commit() >>> len(emily.log) 12 >>> emily.log[11].summary u'Yet another' >>> emily.location = KITCHEN >>> entry = emily.log(u'non-deferred entry', if_changed=True) >>> len(emily.log) 13 >>> entry.summary u'non-deferred entry' >>> emily.log(u'will not write', defer=True, if_changed=True) >>> transaction.commit() >>> len(emily.log) 13 >>> emily.log(u'will not write', defer=True, if_changed=True) >>> emily.location = WORKING >>> emily.log(u'also will not write', defer=True, if_changed=True) >>> emily.log(u'required, deferred', defer=True) >>> len(emily.log) 13 >>> transaction.commit() >>> len(emily.log) 14 >>> emily.log[13].summary u'required, deferred'
当然,所有这些都应该在多个对象存在的情况下工作。
>>> sam = Cat(u'Sam', 20, 4) >>> root['sam'] = sam >>> transaction.commit() >>> sam.weight = 19 >>> sam.log(u'Auto log', defer=True, if_changed=True) >>> sam.log(u'Sam lost weight!', defer=True) >>> sam.log(u'Saw sam today', defer=True) >>> emily.log(u'Auto log', defer=True, if_changed=True) >>> emily.weight = 15 >>> transaction.commit() >>> len(sam.log) 2 >>> sam.log[0].summary u'Sam lost weight!' >>> sam.log[1].summary u'Saw sam today' >>> len(emily.log) 15 >>> emily.log[14].summary u'Auto log'>>> # TEAR DOWN >>> zope.security.management.endInteraction() >>> ztapi.unprovideUtility(zope.app.security.interfaces.IAuthentication)
更改
0.2(2008-05-16)
- 删除了对zc.security的依赖;放宽了对主体id的限制。
0.1.1(2008-04-02)
- 更新了setup.py中的路径,以便安装程序在Linux以外的平台上工作。
0.1(2008-04-02)
- 初始版本(已删除dev状态)