跳转到主要内容

帮助在网页中导航批量结果的辅助工具。

项目描述

批导航提供了一种通过提供URL链接到结果的下页、上页和编号页来在网页中导航批处理结果的方法。

它使用四个查询/POST参数来控制分批。

  • memo:批处理位置的底层存储索引指针的记录。

  • direction:指示memo是否在批处理的开始或结束。

  • start:外观 - 用于计算明显的位置(但请注意,由于批处理重复访问的并发性,实际偏移量可能不同 - 然而,集合不会跳过或显示项目两次。为了与保存的URL兼容,如果memo和direction都缺失,则使用start在集合中进行列表切片。

  • batch:控制每个批处理显示的项目数量。如果它与创建批处理时设置的默认值不同,则将出现。

除非同时传递force_start=True,否则这些值可以在请求中覆盖,这将使start参数(再次,默认为0)始终被选中。

导入

>>> from lazr.batchnavigator import BatchNavigator, ListRangeFactory
>>> from zope.publisher.browser import TestRequest
>>> from zope.publisher.http import HTTPCharsets
>>> from zope.component import getSiteManager
>>> sm = getSiteManager()
>>> sm.registerAdapter(HTTPCharsets)
>>> def build_request(query_string_args=None, method='GET'):
...   if query_string_args is None:
...       query_string = ''
...   else:
...       if getattr(query_string_args, 'items', None) is not None:
...           query_string_args = query_string_args.items()
...       query_string = "&".join(
...           ["%s=%s" % (k,v) for k,v in query_string_args])
...   request = TestRequest(SERVER_URL='http://www.example.com/foo',
...                                  method=method,
...                                  environ={'QUERY_STRING': query_string})
...   request.processInputs()
...   return request

一个示例请求对象

一些示例数据。

>>> reindeer = ['Dasher', 'Dancer', 'Prancer', 'Vixen', 'Comet',
...             'Cupid', 'Donner', 'Blitzen', 'Rudolph']

由于切片大型集合可能非常昂贵,BatchNavigator提供了一种确定批处理边缘的非切片协议。range_factory提供实现了IRangeFactory的对象,并管理此协议。ListRangeFactory是一个简单的内置实现,如果没有提供range_factory,BatchNavigator将使用它。

>>> _ = BatchNavigator(reindeer, build_request(),
...     range_factory=ListRangeFactory(reindeer))

对于文档中的示例,我们让BatchNavigator隐式构建range_factory。

>>> safe_reindeer = reindeer
>>> safe_reindeer_batch_navigator = BatchNavigator(
...     safe_reindeer, build_request(), size=3)

lazr.batchnavigator的一个重要特性是它不倾向于对底层数据集调用len()。len()可能是一个昂贵的操作,提供的好处很少,因此此库将尽量不调用len(),除非绝对必要。为了展示这一点,我们将定义一个Python列表类型的子类,当调用len()时会崩溃。

>>> class ListWithExplosiveLen(list):
...     """A list subclass that doesn't like its len() being called."""
...     def __len__(self):
...         raise RuntimeError

除非另有说明,否则我们将在此测试中独家使用此列表,以验证len()只有在需要时才会调用。

>>> explosive_reindeer = ListWithExplosiveLen(reindeer)
>>> reindeer_batch_navigator = BatchNavigator(
...     explosive_reindeer, build_request(), size=3)

BatchNavigator实现了IBatchNavigator。我们需要在这里使用“安全”的批处理导航器,因为verifyObject调查传递给它的对象的所有方法,包括__len__。

>>> from zope.interface.verify import verifyObject
>>> from lazr.batchnavigator.interfaces import IBatchNavigator
>>> verifyObject(IBatchNavigator, safe_reindeer_batch_navigator)
True

BatchNavigator类提供IBatchNavigatorFactory。这可以用于将批处理导航器工厂注册为实用工具,例如。

>>> from lazr.batchnavigator.interfaces import IBatchNavigatorFactory
>>> verifyObject(IBatchNavigatorFactory, BatchNavigator)
True

您可以要求导航器获取当前显示的结果块(例如,为了在ZPT中渲染而迭代它们)

>>> list(reindeer_batch_navigator.currentBatch())
['Dasher', 'Dancer', 'Prancer']

您可以请求第一个、上一个、下一个和最后一个结果的链接

>>> reindeer_batch_navigator.firstBatchURL()
''
>>> reindeer_batch_navigator.prevBatchURL()
''
>>> reindeer_batch_navigator.nextBatchURL()
'http://www.example.com/foo?memo=3&start=3'

没有方法可以获取到最后一批的URL,除非知道整个列表的长度,所以我们将使用安全批处理导航器来演示lastBatchURL()

>>> safe_reindeer_batch_navigator.lastBatchURL()
'http://www.example.com/foo?direction=backwards&start=6'

当没有更多结果时,下一个链接将为空

>>> request = build_request({"start": "3", "batch": "20"})
>>> last_reindeer_batch_navigator = BatchNavigator(reindeer, request=request)
>>> last_reindeer_batch_navigator.nextBatchURL()
''

即使我们从0和批处理大小之间的某个点开始,第一个和上一个链接也应该出现

>>> request = build_request({"start": "2", "batch": "3"})
>>> last_reindeer_batch_navigator = BatchNavigator(reindeer, request=request)

在这里,我们还可以看到批处理参数作为URL的一部分出现。这是因为请求在创建Batch对象时请求了与默认值不同的尺寸,默认情况下是5。

>>> last_reindeer_batch_navigator.firstBatchURL()
'http://www.example.com/foo?batch=3'
>>> last_reindeer_batch_navigator.prevBatchURL()
'http://www.example.com/foo?batch=3&direction=backwards&memo=2'

这也可以与查询字符串中的其他值一起工作

>>> request = build_request({'fnorb': 'bar',
...                          'start': '3',
...                          'batch': '3'})
>>> reindeer_batch_navigator_with_qs = BatchNavigator(
...     reindeer, request, size=3)
>>> safe_reindeer_batch_navigator_with_qs = BatchNavigator(
...     safe_reindeer, request, size=3)

在这种情况下,我们使用默认大小为“3”创建了BatchNavigator,请求的确实是每批次的那个数量,因此,我们不需要在URL中显示“批次”。

>>> reindeer_batch_navigator_with_qs.firstBatchURL()
'http://www.example.com/foo?fnorb=bar'
>>> reindeer_batch_navigator_with_qs.prevBatchURL()
'http://www.example.com/foo?fnorb=bar&direction=backwards&memo=3'
>>> reindeer_batch_navigator_with_qs.nextBatchURL()
'http://www.example.com/foo?fnorb=bar&memo=6&start=6'

(同样,不知道整个列表的大小,就无法获取最后一个批次。)

>>> safe_reindeer_batch_navigator_with_qs.lastBatchURL()
'http://www.example.com/foo?fnorb=bar&direction=backwards&start=6'

参数force_start允许你忽略请求中的起始值。例如,当过滤器发生变化,需要从0开始重新启动时,这非常有用。

>>> reindeer_batch_navigator_with_qs = BatchNavigator(
...     reindeer, request, size=3, force_start=True)
>>> reindeer_batch_navigator_with_qs.currentBatch().start
0
>>> reindeer_batch_navigator_with_qs.nextBatchURL()
'http://www.example.com/foo?fnorb=bar&memo=3&start=3'
>>> reindeer[:3] == list(reindeer_batch_navigator_with_qs.currentBatch())
True

我们确保在POST操作中也会观察到URL中提供的批量参数。

>>> request = build_request({'fnorb': 'bar',
...                          'start': '3',
...                          'batch': '3'}, method='POST')
>>> reindeer_batch_navigator_post_with_qs = BatchNavigator(
...     reindeer, request)
>>> reindeer_batch_navigator_post_with_qs.start
3
>>> reindeer_batch_navigator_post_with_qs.nextBatchURL()
'http://www.example.com/foo?fnorb=bar&batch=3&memo=6&start=6'

我们确保在URL中提供的多个大小和批量参数不会使应用程序崩溃。首选第一个。

>>> request = build_request(
...     [('batch', '1'), ('batch', '7'), ('start', '2'), ('start', '10')])
>>> navigator = BatchNavigator(reindeer, request=request)
>>> navigator.nextBatchURL()
'http://www.example.com/foo?batch=1&memo=3&start=3'

批量参数必须是正数。其他数字将被忽略,并使用默认的批量大小。

>>> from six.moves.urllib.parse import parse_qs
>>> request = build_request({'batch': '0'})
>>> navigator = BatchNavigator(range(99), request=request)
>>> print('batch' in parse_qs(navigator.nextBatchURL()))
False
>>> request = build_request({'batch': '-1'})
>>> navigator = BatchNavigator(range(99), request=request)
>>> print('batch' in parse_qs(navigator.nextBatchURL()))
False

空批次

你也可以创建一个没有任何项目的空批次。

>>> null_batch_navigator = BatchNavigator(
...     None, build_request(), size=3)
>>> null_batch_navigator.firstBatchURL()
''
>>> null_batch_navigator.nextBatchURL()
''
>>> null_batch_navigator.prevBatchURL()
''
>>> null_batch_navigator.lastBatchURL()
''
>>> null_batch_navigator = BatchNavigator(
...     [], build_request(), size=3)
>>> null_batch_navigator.firstBatchURL()
''
>>> null_batch_navigator.nextBatchURL()
''
>>> null_batch_navigator.prevBatchURL()
''
>>> null_batch_navigator.lastBatchURL()
''

待办事项

  • 当起始值超过结束值时崩溃

  • 孤儿

  • 重叠

支持不带 __len__ 的结果

某些结果对象没有实现 __len__,因为通常Python代码假设 __len__ 是廉价的。例如,SQLObject和Storm结果集都有这种行为,以便明确知道获取长度是一个非简单操作。

为了支持这些对象,批量会查找结果集中的 __len__。如果不存在,它将结果适配到 zope.interface.common.sequence.IFiniteSequence 并使用那个 __len__。

>>> class ExampleResultSet(object):
...     def __init__(self, results):
...         self.stub_results = results
...     def count(self):
...         # imagine this actually returned
...         return len(self.stub_results)
...     def __getitem__(self, ix):
...         return self.stub_results[ix] # also works with slices
...     def __iter__(self):
...         return iter(self.stub_results)
...
>>> from zope.interface import implementer
>>> from zope.component import adapter, getSiteManager
>>> from zope.interface.common.sequence import IFiniteSequence
>>> @adapter(ExampleResultSet)
... @implementer(IFiniteSequence)
... class ExampleAdapter(ExampleResultSet):
...     def __len__(self):
...         return self.stub_results.count()
...
>>> sm = getSiteManager()
>>> sm.registerAdapter(ExampleAdapter)
>>> example = ExampleResultSet(safe_reindeer)
>>> example_batch_navigator = BatchNavigator(
...     example, build_request(), size=3)
>>> example_batch_navigator.currentBatch().total()
9

只获取所需的内容

对于批量大结果集的性能来说,也很重要,批量只获取结果的一部分,而不是访问全部。

>>> class ExampleResultSet(ExampleResultSet):
...     def __init__(self, results):
...         super(ExampleResultSet, self).__init__(results)
...         self.getitem_history = []
...     def __getitem__(self, ix):
...         self.getitem_history.append(ix)
...         return super(ExampleResultSet, self).__getitem__(ix)
...
>>> example = ExampleResultSet(reindeer)
>>> example_batch_navigator = BatchNavigator(
...     example, build_request(), size=3)
>>> reindeer[:3] == list(example_batch_navigator.currentBatch())
True
>>> example.getitem_history
[slice(0, 4, None)]

请注意,尽管批量的大小与请求的一致,但底层列表中包含一个比必要的项目多。这是为了便于确定给定的批次是否是列表中的最后一个批次,而无需显式查找列表的长度(可能是一个昂贵的操作)。

添加回调函数

有时,当确定批次值后,需要调用某个函数。这是需要为每个批次执行后续查询的情况,而执行整个结果集的查询既不理想或过于昂贵。

回调函数必须定义两个参数。第一个是批处理导航器对象本身,第二个是当前批次。当批处理导航器构造,并确定当前批次时,回调函数只被调用一次。

>>> def print_callback(context, batch):
...     for item in batch:
...         print(item)
>>> reindeer_batch_navigator = BatchNavigator(
...     reindeer, build_request(), size=3, callback=print_callback)
Dasher
Dancer
Prancer
>>> request = build_request({"start": "3", "batch": "20"})
>>> last_reindeer_batch_navigator = BatchNavigator(
...     reindeer, request=request, callback=print_callback)
Vixen
Comet
Cupid
Donner
Blitzen
Rudolph

最有可能的是,回调函数将绑定到一个视图类。通过将批处理导航器本身作为回调的上下文,可以添加额外的成员变量。当批处理导航器成为分页模板的上下文时,这非常有用。

>>> class ReindeerView:
...     def constructReindeerFromAtoms(self, context, batch):
...         # some significantly slow process
...         view.built_reindeer = list(batch)
...     def batchedReindeer(self):
...         return BatchNavigator(
...            reindeer, build_request(), size=3,
...            callback=self.constructReindeerFromAtoms)
>>> view = ReindeerView()
>>> batch_navigator = view.batchedReindeer()
>>> print(view.built_reindeer)
['Dasher', 'Dancer', 'Prancer']
>>> print(list(batch_navigator.currentBatch()))
['Dasher', 'Dancer', 'Prancer']

最大批量大小

由于批量大小在URL中公开,用户可以调整批量参数以获取更多结果。由于这可能耗尽服务器资源,因此对批量大小设置了上限。如果请求的批量参数高于此值,将引发 InvalidBatchSizeError。

>>> from lazr.batchnavigator.interfaces import InvalidBatchSizeError
>>> class DemoBatchNavigator(BatchNavigator):
...     max_batch_size = 5
...
>>> request = build_request({"start": "0", "batch": "20"})
>>> test_raises(
...     InvalidBatchSizeError, DemoBatchNavigator,
...     reindeer, request=request)
Maximum for "batch" parameter is 5.

URL参数

通常,当前页面URL中传递的任何参数都会在批量导航器的链接中重新生成。一个“短暂”参数是仅与当前页面请求相关的参数,不应传递给后续请求。

在下一个批处理导航器中,页面的URL中出现了两个参数:“noisy”和“quiet”。

>>> request_parameters = {
...     'quiet': 'ssht',
...     'noisy': 'HELLO',
...     }
>>> request_with_parameters = build_request(request_parameters)

其中一个参数“quiet”是短暂的。还有一个短暂的参数称为“absent”,但它没有被传递到我们当前的页面请求中。

>>> def build_navigator(list):
...     return BatchNavigator(
...         list, request_with_parameters, size=3,
...         transient_parameters=['quiet', 'absent'])
>>> navigator_with_parameters = build_navigator(reindeer)
>>> safe_navigator_with_parameters = build_navigator(safe_reindeer)

在这三个参数中,只有“noisy”在批量导航器生成的链接中重复。

>>> navigator_with_parameters.nextBatchURL()
'http://www.example.com/foo?noisy=HELLO&memo=3&start=3'
>>> safe_navigator_with_parameters.lastBatchURL()
'http://www.example.com/foo?noisy=HELLO&direction=backwards&start=6'

瞬态参数被省略了,一开始就没有传递进来的参数也不会神奇地出现。

批量标题

批量值通常是某种对象,如错误。BatchNavigator的标题属性包含要显示的对象的描述。

>>> safe_reindeer_batch_navigator.heading
'results'

当批量中只有一个项目时,会返回标题的单数形式。

>>> navigator = BatchNavigator(['only-one'], request=request)
>>> navigator.heading
'result'

(访问.heading会导致在底层列表上调用len(),这就是为什么我们必须使用安全的批量导航器。理论上,这可以优化,但实际上并没有太大意义,因为标题总是紧随底层列表的实际长度之前,例如“10个结果”。由于无论如何都会调用len(),并且其值被缓存,第二次调用len()不会影响性能。)

可以通过传递标题的单数和复数形式来设置标题。批量导航将根据批量中的总项目数返回适当的标题。

>>> navigator = BatchNavigator(safe_reindeer, request=request)
>>> navigator.setHeadings('bug', 'bugs')
>>> navigator.heading
'bugs'
>>> navigator = BatchNavigator(['only-one'], request=request)
>>> navigator.setHeadings('bug', 'bugs')
>>> navigator.heading
'bug'

(清理)

>>> sm.unregisterAdapter(HTTPCharsets)
True
>>> sm.unregisterAdapter(ExampleAdapter)
True

lazr.batchnavigator的新闻

2.0.0 (2022-11-30)

  • 放弃对Python 2.7的支持。

  • 声明支持Python 3.9、3.10和3.11。

  • 添加配置。

  • 在Read the Docs上发布文档。

  • 通过应用black、isort和flake8。

  • 通过woke pre-commit钩子应用包容性命名。

1.3.1 (2021-09-13)

  • 调整版本策略以避免导入pkg_resources,这在大型环境中较慢。

1.3.0 (2019-11-04)

  • 从buildout切换到tox。

  • 添加Python 3支持。

1.2.11 (2015-04-09)

  • 如果切片形式为[x:x],则保存查询。

1.2.10 (2011-09-14)

  • 将结果集的大致长度计算委托给IRangeFactory。

1.2.9 (2011-08-25)

  • 当反向批量最初太短,并且从结果集中添加另一个块时,_Batch,sliced_list()不再使用已检索块的memo值。

  • 不要使用参数start来确定是否存在上一个/下一个批量;不要依赖于len(resultset)来确定批量的实际大小。

  • 避免在空结果集上有负的起始索引。

1.2.7 (2011-07-18)

  • 仅通过范围工厂的方法在类_Batch中检索结果集的切片。

1.2.6 (2011-07-28)

  • 修复了处理反向批量的错误,这些批量返回的元素少于预期。

  • 在BatchNavigator.generateBatchURL()中对所有查询参数进行URL编码。

1.2.5 (2011-07-13)

  • 允许通过单个前缀更改所有变量名。

1.2.4 (2011-04-11)

  • 允许重写determineSize以控制子类中批量默认值和具体大小的确定方式。

  • (一旦切片)列表化,而不是假设批量切片会遵守完整的列表协议。

1.2.3 (2011-04-06)

  • 添加IRangeFactory和使用后端数据库提示高效检索页面的能力。

  • 移除糟糕的缩放getBatchURLs方法。

1.2.2 (2010-08-19)

  • 当当前批量是最后一个(或唯一)批量时,len()调用便宜。

  • 在生成导航器URL时避免调用len()。

1.2.1 (2010-08-12)

  • 修复了批量len()的错误,当批量之前已被迭代过

1.2.0 (2010-08-05)

  • 尽可能避免在底层序列上调用len()。

  • 当批量超出范围时,endNumber返回None。

1.1.1 (2010-05-10)

  • 忽略负的批量大小。

1.1 (2009-08-31)

  • 移除对bzr和egg_info的构建依赖。

  • 从setup.py中移除sys.path黑客攻击以获取__version__。

1.0 (2009-03-24)

  • PyPI上的首次发布。

项目详情


下载文件

下载您平台上的文件。如果您不确定该选择哪个,请了解更多关于安装包的信息。

源分发

lazr.batchnavigator-2.0.0.tar.gz (50.9 kB 查看哈希值)

上传时间

构建分发

lazr.batchnavigator-2.0.0-py3-none-any.whl (50.3 kB 查看哈希值)

上传时间 Python 3

由以下提供支持