Kea2-python 0.3.0__py3-none-any.whl → 0.3.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of Kea2-python might be problematic. Click here for more details.

kea2/u2Driver.py CHANGED
@@ -1,12 +1,14 @@
1
+ import functools
1
2
  import random
2
3
  import socket
4
+ import time
3
5
  from time import sleep
4
6
  import uiautomator2 as u2
5
7
  import adbutils
6
8
  import types
7
9
  import rtree
8
10
  import re
9
- from typing import Any, Dict, List, Union
11
+ from typing import Any, Dict, List, Union, Optional
10
12
  from http.client import HTTPResponse
11
13
  from lxml import etree
12
14
  from .absDriver import AbstractScriptDriver, AbstractStaticChecker, AbstractDriver
@@ -205,6 +207,122 @@ class StaticU2UiObject(u2.UiObject):
205
207
  def __getattr__(self, attr):
206
208
  return getattr(super(), attr)
207
209
 
210
+ """
211
+ The definition of XpathStaticChecker
212
+ """
213
+ class StaticXpathUiObject(u2.xpath.XPathSelector):
214
+ def __init__(self, session, selector):
215
+ self.session: U2StaticDevice = session
216
+ self.selector = selector
217
+
218
+ @property
219
+ def exists(self):
220
+ source = self.session.get_page_source()
221
+ return len(self.selector.all(source)) > 0
222
+
223
+ def __and__(self, value) -> 'StaticXpathUiObjectr':
224
+ s = u2.xpath.XPathSelector(self.selector)
225
+ s._next_xpath = u2.xpath.XPathSelector.create(value.selector)
226
+ s._operator = u2.xpath.Operator.AND
227
+ s._parent = self.selector._parent
228
+ self.selector = s
229
+ return self
230
+
231
+ def __or__(self, value) -> 'StaticXpathUiObject':
232
+ s = u2.xpath.XPathSelector(self.selector)
233
+ s._next_xpath = u2.xpath.XPathSelector.create(value.selector)
234
+ s._operator = u2.xpath.Operator.OR
235
+ s._parent = self.selector._parent
236
+ self.selector = s
237
+ return self
238
+
239
+ def selector_to_xpath(self, selector: u2.xpath.XPathSelector) -> str:
240
+ """
241
+ Convert an XPathSelector to a standard XPath expression.
242
+
243
+ Args:
244
+ selector: The XPathSelector object to convert.
245
+
246
+ Returns:
247
+ A standard XPath expression as a string.
248
+ """
249
+
250
+ def _handle_path(path):
251
+ if isinstance(path, u2.xpath.XPathSelector):
252
+ return self.selector_to_xpath(path)
253
+ elif isinstance(path, u2.xpath.XPath):
254
+ return str(path)
255
+ else:
256
+ return path
257
+
258
+ base_xpath = _handle_path(selector._base_xpath)
259
+ base_xpath = base_xpath.replace('//*', './/node')
260
+
261
+ if selector._operator is None:
262
+ return base_xpath
263
+ else:
264
+ print("Unsupported operator: {}".format(selector._operator))
265
+ return "//error"
266
+
267
+ def xpath(self, _xpath: Union[list, tuple, str]) -> 'StaticXpathUiObject':
268
+ """
269
+ add xpath to condition list
270
+ the element should match all conditions
271
+
272
+ Deprecated, using a & b instead
273
+ """
274
+ if isinstance(_xpath, (list, tuple)):
275
+ self.selector = functools.reduce(lambda a, b: a & b, _xpath, self)
276
+ else:
277
+ self.selector = self.selector & _xpath
278
+ return self
279
+
280
+ def child(self, _xpath: str) -> "StaticXpathUiObject":
281
+ """
282
+ add child xpath
283
+ """
284
+ if self.selector._operator or not isinstance(self.selector._base_xpath, u2.xpath.XPath):
285
+ raise u2.xpath.XPathError("can't use child when base is not XPath or operator is set")
286
+ new = self.selector.copy()
287
+ new._base_xpath = self.selector._base_xpath.joinpath(_xpath)
288
+ self.selector = new
289
+ return self
290
+
291
+ def get(self, timeout=None) -> "XMLElement":
292
+ """
293
+ Get first matched element
294
+
295
+ Args:
296
+ timeout (float): max seconds to wait
297
+
298
+ Returns:
299
+ XMLElement
300
+
301
+ """
302
+ if not self.exists:
303
+ return None
304
+ return self.get_last_match()
305
+
306
+ def get_last_match(self) -> "u2.xpath.XMLElement":
307
+ return self.selector.all(self.selector._last_source)[0]
308
+
309
+ def parent_exists(self, xpath: Optional[str] = None):
310
+ el = self.get()
311
+ if el is None:
312
+ return False
313
+ element = el.parent(xpath) if hasattr(el, 'parent') else None
314
+ return True if element is not None else False
315
+
316
+ def __getattr__(self, key: str):
317
+ """
318
+ In IPython console, attr:_ipython_canary_method_should_not_exist_ will be called
319
+ So here ignore all attr startswith _
320
+ """
321
+ if key.startswith("_"):
322
+ raise AttributeError("Invalid attr", key)
323
+ if not hasattr(u2.xpath.XMLElement, key):
324
+ raise AttributeError("Invalid attr", key)
325
+ return getattr(super(), key)
208
326
 
209
327
  def _get_bounds(raw_bounds):
210
328
  pattern = re.compile(r"\[(-?\d+),(-?\d+)\]\[(-?\d+),(-?\d+)\]")
@@ -299,7 +417,8 @@ class U2StaticDevice(u2.Device):
299
417
  def xpath(self) -> u2.xpath.XPathEntry:
300
418
  def get_page_source(self):
301
419
  # print("[Debug] Using static get_page_source method")
302
- return u2.xpath.PageSource.parse(self._d.xml_raw)
420
+ xml_raw = etree.tostring(self._d.xml, encoding='unicode')
421
+ return u2.xpath.PageSource.parse(xml_raw)
303
422
  xpathEntry = _XPathEntry(self)
304
423
  xpathEntry.get_page_source = types.MethodType(
305
424
  get_page_source, xpathEntry
@@ -316,10 +435,14 @@ class _XPathEntry(u2.xpath.XPathEntry):
316
435
  self.xpath = None
317
436
  super().__init__(d)
318
437
 
319
- def __call__(self, xpath, source = None):
438
+ # def __call__(self, xpath, source = None):
320
439
  # TODO fully support xpath in widget.block.py
321
- self.xpath = xpath
322
- return super().__call__(xpath, source)
440
+ # self.xpath = xpath
441
+ # return super().__call__(xpath, source)
442
+ def __call__(self, xpath, source=None):
443
+ ui = StaticXpathUiObject(session=self, selector=u2.xpath.XPathSelector(xpath, source=source))
444
+ return ui
445
+
323
446
 
324
447
 
325
448
  class U2StaticChecker(AbstractStaticChecker):
kea2/utils.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import logging
2
2
  import os
3
3
  from pathlib import Path
4
+ import traceback
4
5
  from typing import TYPE_CHECKING
5
6
 
6
7
  import time
@@ -26,6 +27,9 @@ def getLogger(name: str) -> logging.Logger:
26
27
  return logger
27
28
 
28
29
 
30
+ logger = getLogger(__name__)
31
+
32
+
29
33
  def singleton(cls):
30
34
  _instance = {}
31
35
 
@@ -60,12 +64,49 @@ def getProjectRoot():
60
64
  return cur_dir
61
65
 
62
66
 
63
- def timer(func):
64
- @wraps(func)
65
- def wrapper(*args, **kwargs):
66
- start_time = time.time()
67
- result = func(*args, **kwargs)
68
- end_time = time.time()
69
- print(f"Function '{func.__name__}' executed in {(end_time - start_time):.4f} seconds.")
70
- return result
71
- return wrapper
67
+ def timer(log_info: str=None):
68
+ """ ### Decorator to measure the execution time of a function.
69
+
70
+ This decorator can be used to wrap functions where you want to log the time taken for execution
71
+
72
+ ### Usage:
73
+ - @timer("Function execution took %cost_time seconds.")
74
+ - @timer() # If no log_info is provided, it will print the function name and execution time.
75
+
76
+ `%cost_time` will be replaced with the actual time taken for execution.
77
+ """
78
+ def accept(func):
79
+ @wraps(func)
80
+ def wrapper(*args, **kwargs):
81
+ start_time = time.time()
82
+ result = func(*args, **kwargs)
83
+ end_time = time.time()
84
+ if log_info:
85
+ logger.info(log_info.replace(r"%cost_time", f"{end_time - start_time:.4f}"))
86
+ else:
87
+ logger.info(f"Function '{func.__name__}' executed in {(end_time - start_time):.4f} seconds.")
88
+ return result
89
+ return wrapper
90
+ return accept
91
+
92
+
93
+ def catchException(log_info: str):
94
+ """ ### Decorator to catch exceptions and print log info.
95
+
96
+ This decorator can be used to wrap functions that may raise exceptions,
97
+ allowing you to log a message when the exception is raised.
98
+
99
+ ### Usage:
100
+ - @catchException("An error occurred in the function ****.")
101
+ """
102
+ def accept(func):
103
+ @wraps(func)
104
+ def wrapper(*args, **kwargs):
105
+ try:
106
+ return func(*args, **kwargs)
107
+ except Exception as e:
108
+ logger.info(log_info)
109
+ tb = traceback.format_exception(type(e), e, e.__traceback__.tb_next)
110
+ print(''.join(tb), end='', flush=True)
111
+ return wrapper
112
+ return accept
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Kea2-python
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: A python library for supporting and customizing automated UI testing for mobile apps
5
5
  Author-email: Xixian Liang <xixian@stu.ecnu.edu.cn>
6
6
  Requires-Python: >=3.8
@@ -10,6 +10,7 @@ Requires-Dist: rtree>=1.3.0
10
10
  Requires-Dist: jinja2>=3.0.0
11
11
  Requires-Dist: uiautomator2>=3.3.3
12
12
  Requires-Dist: adbutils>=2.9.3
13
+ Requires-Dist: setuptools>=75.3.2
13
14
  Dynamic: license-file
14
15
 
15
16
 
@@ -17,6 +18,7 @@ Dynamic: license-file
17
18
  [![PyPI](https://img.shields.io/pypi/v/kea2-python.svg)](https://pypi.python.org/pypi/kea2-python)
18
19
  [![PyPI Downloads](https://static.pepy.tech/badge/kea2-python)](https://pepy.tech/projects/kea2-python)
19
20
  ![Python](https://img.shields.io/badge/python-3.8%2B-blue)
21
+ [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/ecnusse/Kea2)
20
22
 
21
23
  <div>
22
24
  <img src="https://github.com/user-attachments/assets/84e47b87-2dd2-4d7e-91d1-e8c1d1db0cf4" style="border-radius: 14px; width: 20%; height: 20%;"/>
@@ -31,6 +33,13 @@ The group has reached its capacity. Please contact Xixian Liang at [xixian@stu.e
31
33
 
32
34
  ## About
33
35
 
36
+ <div align="center">
37
+ <img src="docs/images/kea2_logo.png" alt="kea_logo" style="border-radius: 14px; width: 20%; height: 20%;"/>
38
+ </div>
39
+ <div align="center">
40
+ <a href="https://en.wikipedia.org/wiki/Kea">Kea2's logo: A large parrot skilled in finding "bugs"</a>
41
+ </div>
42
+
34
43
  Kea2 is an easy-to-use tool for fuzzing mobile apps. Its key *novelty* is able to fuse automated UI testing with scripts (usually written by human), thus empowering automated UI testing with human intelligence for effectively finding *crashing bugs* as well as *non-crashing functional (logic) bugs*.
35
44
 
36
45
  Kea2 is currently built on top of [Fastbot](https://github.com/bytedance/Fastbot_Android), *an industrial-strength automated UI testing tool*, and [uiautomator2](https://github.com/openatx/uiautomator2), *an easy-to-use and stable Android automation library*.
@@ -40,7 +49,7 @@ Kea2 currently targets [Android](https://en.wikipedia.org/wiki/Android_(operatin
40
49
 
41
50
  <div align="center">
42
51
  <div style="max-width:80%; max-height:80%">
43
- <img src="docs/intro.png" style="border-radius: 14px; width: 80%; height: 80%;"/>
52
+ <img src="docs/images/intro.png" style="border-radius: 14px; width: 80%; height: 80%;"/>
44
53
  </div>
45
54
  </div>
46
55
 
@@ -60,11 +69,17 @@ Kea2 currently targets [Android](https://en.wikipedia.org/wiki/Android_(operatin
60
69
  | **Finding crashes in deep states** | | :+1: | :+1: |
61
70
  | **Finding non-crashing functional (logic) bugs** | | | :+1: |
62
71
 
63
- ## Kea2's Known Users
72
+ ## Kea2's Users
73
+
74
+ Kea2 (and its idea) has been used/integrated by
64
75
 
65
- [OPay Business](https://play.google.com/store/apps/details?id=team.opay.pay.merchant.service) --- a financial & payment app
76
+ - [OPay Business](https://play.google.com/store/apps/details?id=team.opay.pay.merchant.service) --- a financial & payment app. OPay uses Kea2 for regression testing on POS machines and mobile devices.
66
77
 
67
- We are glad to hear if you are using Kea2 for your app.
78
+ - [WeChat's iExplorer]() --- WeChat's in-house testing platform
79
+
80
+ - [ByteDance's Fastbot](https://github.com/bytedance/Fastbot_Android)
81
+
82
+ Please let us know and willing to hear your feedback/questions if you are also using Kea2.
68
83
 
69
84
  ## Design & Roadmap
70
85
  Kea2 currently works with:
@@ -82,7 +97,7 @@ In the future, Kea2 will be extended to support
82
97
 
83
98
  Running environment:
84
99
  - support Windows, MacOS and Linux
85
- - python 3.8+, Android 5.0+ (Android SDK installed)
100
+ - python 3.8+, Android 5.0~16.0 (Android SDK installed)
86
101
  - **VPN closed** (Features 2 and 3 required)
87
102
 
88
103
  Install Kea2 by `pip`:
@@ -193,7 +208,7 @@ In Feature 3, a script is composed of three elements:
193
208
  In a social media app, message sending is a common feature. On the message sending page, the `send` button should always appears when the input box is not empty (i.e., has some message).
194
209
 
195
210
  <div align="center">
196
- <img src="docs/socialAppBug.png" style="border-radius: 14px; width:30%; height:40%;"/>
211
+ <img src="docs/images/socialAppBug.png" style="border-radius: 14px; width:30%; height:40%;"/>
197
212
  </div>
198
213
 
199
214
  <div align="center">
@@ -230,7 +245,55 @@ You can find the [user manual](docs/manual_en.md), which includes:
230
245
  - How to run Kea2 and Kea2's command line options
231
246
  - How to find and understand Kea2's testing results
232
247
  - How to [whitelist or blacklist](docs/blacklisting.md) specific activities, UI widgets and UI regions during fuzzing
233
- - [Common Q&A for PBT and Kea2](https://sy8pzmhmun.feishu.cn/wiki/SLGwwqgzIiEuC3kwmV8cSZY0nTg?from=from_copylink)
248
+ - [Q&A for Kea2 and PBT (对Kea2和PBT技术的常见问题和回答)](https://sy8pzmhmun.feishu.cn/wiki/SLGwwqgzIiEuC3kwmV8cSZY0nTg?from=from_copylink)
249
+
250
+ Some blogs on Kea/Kea2 (in Chinese):
251
+ - [别再苦哈哈写测试脚本了,生成它们吧!(一)](https://mp.weixin.qq.com/s/R2kLCkXpDjpa8wCX4Eidtg)
252
+ - [别再苦哈哈写测试脚本了,生成它们吧!(二)](https://mp.weixin.qq.com/s/s4WkdstNcKupu9OP8jeOXw)
253
+ - [别再苦哈哈写测试脚本了,生成它们吧!(三)](https://mp.weixin.qq.com/s/BjXyo-xJRmPB_sCc4pmh8g)
254
+ - [2025 Let’s GoSSIP 软件安全暑期学校预告第一弹——Kea2](https://mp.weixin.qq.com/s/8_0_GNNin8E5BqTbJU33wg)
255
+ - [功能性质驱动的测试技术:下一代GUI自动化测试技术](https://appw8oh6ysg4044.xet.citv.cn/p/course/video/v_6882fa14e4b0694ca0ec0a1b) --- 视频回放&PPT@MTSC 2025
256
+
257
+ 工业界对Kea2的理解和评价(点击箭头查看详情):
258
+
259
+ <details>
260
+ <summary>Kea2的性质是什么含义?Kea2意义和价值是什么?</summary>
261
+
262
+ kea2 其实是一个工具,它是python+u2+fastbot的集合体。 它本身更像是一台装好了发动机和轮子的汽车底盘。
263
+
264
+ 性质是苏老师他们团队提出的一个概念, 转换到测试领域的实际工作中,性质对应的是最小单位的功能(原子级功能),性质的依赖条件很少或没有,它可以自身运行。一个典型的性质就是登录,它仅仅具有输入用户名,输入密码,提交。再举个例子,给视频点个赞,也就是简单的两三步。就是一个性质。
265
+
266
+ 性质与kea2结合的意义是在于解决过去使用appium过重的问题。用appium去测试一个性质通常要写很多行的代码,引导界面到达性质的位置。但使用kea2,就只需要编写性质,如何到其所在的位置是交给fastbot和它的学习算法来搞定的。
267
+
268
+ kea2另个重大的价值是,它解决了上述思想所需要的技术支撑,比appium更轻量的UI编写方式,fastbot编写性质的能力不足,以及无法编写逻辑和断言。整体上是保留了fastbot以往的优秀品质,完善了其不足和短板。
269
+
270
+ 简而言之,需要做传统的编排型的功能测试,仍然使用appium,使用kea2也行,但你感觉不到它的价值。本身有需要做混沌测试,模糊测试,兼容性测试。那么强烈,强烈推荐kea2。kea2更偏探索性测试而非编排型。
271
+ </details>
272
+
273
+ <details>
274
+ <summary>kea2组成是什么?kea2的核心作用?kea2做了什么?</summary>
275
+
276
+ kea2 组成:
277
+
278
+ fastbot -- fuzz测试引擎,负责跑路。
279
+ u2 -- 负责进行业务空间的操作。与使用selenium,appium,没什么区别。
280
+ python -- u2的操作,逻辑的编写,定制化的实现。
281
+
282
+ kea2的核心作用:
283
+
284
+ 提供了条件触发器。 在FB跑路的时候,会不停遍历条件触发器,一旦触发,挂起FB,开始执行触发器指定的 ui test 及 assert。执行完毕,继续切回FB跑路。
285
+
286
+ hea2做了什么:
287
+
288
+ 替换了FB的条件触发功能。
289
+ 替换了FB的黑名单,黑控件功能。
290
+ 替换了FB剪枝功能。
291
+ 增加了多元化的元素空间操作能力。
292
+ 增加了fuzz测试中的 逻辑设定。
293
+ 增加了断言能力。
294
+ 增加了元素操作能力。
295
+ </details>
296
+
234
297
 
235
298
  ## Open-source projects used by Kea2
236
299
 
@@ -1,16 +1,16 @@
1
1
  kea2/__init__.py,sha256=JFJjqgf5KB4bXUFQ3upkEug0cFMFIk9p3OHV9vzulHw,75
2
2
  kea2/absDriver.py,sha256=NzmsLs1Ojz-yEXctGAqj7aKBwAQW19zd83l65RABCe8,1288
3
3
  kea2/adbUtils.py,sha256=zi0T0_g44xZQZe3XYzBsuh7VTHpdZ4dd6yKi-p7BTYI,19939
4
- kea2/bug_report_generator.py,sha256=eorBNJKJ4TgM5cL1_QLOGPgfISvbRIUb8ns87gjD9IM,43050
4
+ kea2/bug_report_generator.py,sha256=qHwHupANp5-IZXlpSnYy5rkf07BXu2QvvfWZzJWtc6k,42590
5
5
  kea2/cli.py,sha256=MccJhY8VPZa1rMAbirm3dmbzDx1lf2w3zrpF1ZUq2m0,6481
6
- kea2/fastbotManager.py,sha256=gQ3FI1AezCxZ8NorOOW9_K6ASWpRTYTLpJ7eR1R4-8Y,7973
7
- kea2/keaUtils.py,sha256=UFZo6iT03sXL3tDoTCWza8RaqcVnTRjSqWdBMrc49Qc,25904
6
+ kea2/fastbotManager.py,sha256=pGuAH-X26nOy2Qj6Hg5aoMxJx4KfsRk6bj6nhcPENGc,8158
7
+ kea2/keaUtils.py,sha256=KRBYI6Vn36liKj8jFSH0W-ktGgHr-0VdC3xiX_mCF5E,25617
8
8
  kea2/kea_launcher.py,sha256=M9TMCdgkteCWO5iBxJRDRvb7dikanoPf3jdicCEurU8,6268
9
9
  kea2/logWatcher.py,sha256=Dp6OzvLSuWYw0AqdcPDqfotaRZQgpF8S49LInGsAWp8,2599
10
- kea2/report_merger.py,sha256=YOlxK20dsR2OU-LtyGzVrMH55j_wdfWhvTvq7ocARMc,23901
10
+ kea2/report_merger.py,sha256=0b-g59pdrsJDwVNek02ouu1utuu_Sd_1oEMQa4e23xI,24884
11
11
  kea2/resultSyncer.py,sha256=9kb3H0Innwj7oPboDDTf97nbjam7EP6HYxywR9B8SvM,2437
12
- kea2/u2Driver.py,sha256=BZMQOQtYX2Oo6nXRPjIspRlc_TpPo_tgGo2fOLDYYMo,15614
13
- kea2/utils.py,sha256=QXaiUhOstcKyVl0TZOuVB8SaxVVK1lJGpWvhJ4ddWQY,1817
12
+ kea2/u2Driver.py,sha256=SfHkUAp6uHHSllPZmKs7C8JaEGGuX9DPWgshiqyWI5k,19775
13
+ kea2/utils.py,sha256=x0VTGweW-XShG9MuwZDMP13SBg8jQXYa7e0AXdwYX38,3270
14
14
  kea2/assets/fastbot-thirdpart.jar,sha256=0SZ_OoZFWDGMnazgXKceHgKvXdUDoIa3Gb2bcifaikk,85664
15
15
  kea2/assets/framework.jar,sha256=rTluOJJKj2DFwh7ascXso1udYdWv00BxBwSQ3Vmv-fw,1149240
16
16
  kea2/assets/kea2-thirdpart.jar,sha256=HYdtG2gqDLuLb72dpK3lX-Y6QUNTrJ-bfQopU5aWpfo,359346
@@ -28,11 +28,11 @@ kea2/assets/fastbot_libs/arm64-v8a/libfastbot_native.so,sha256=tAFrG73pJi7XakRxS
28
28
  kea2/assets/fastbot_libs/armeabi-v7a/libfastbot_native.so,sha256=UV8bhaiPoPKdd3q0vj3kSZqPR9anllai_tz_2QkMMbQ,1335372
29
29
  kea2/assets/fastbot_libs/x86/libfastbot_native.so,sha256=k-aw1gEXRWMKZRNHIggKNuZy0wC1y2BnveJGEIO6rbo,2036856
30
30
  kea2/assets/fastbot_libs/x86_64/libfastbot_native.so,sha256=tiofhlf4uMQcU5WAvrdLgTBME0lb83hVUoGtTwxmE8A,2121416
31
- kea2/templates/bug_report_template.html,sha256=JPELW0QcDkWqabkkUqxRX6XXERKuNGVitML6ZRUKkJU,146879
32
- kea2/templates/merged_bug_report_template.html,sha256=mMq1Vavd_wCDvommpO4C5Z1Hejjn4WZre-KSDXD74LY,110298
33
- kea2_python-0.3.0.dist-info/licenses/LICENSE,sha256=nM9PPjcsXVo5SzNsjRqWgA-gdJlwqZZcRDSC6Qf6bVE,2034
34
- kea2_python-0.3.0.dist-info/METADATA,sha256=ZXOHCsDg7EPz3o-cuuzJ5nyMld_Nek1bfi4cGB0zKCI,14588
35
- kea2_python-0.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
36
- kea2_python-0.3.0.dist-info/entry_points.txt,sha256=mFX06TyxXiUAJQ6JZn8QHzfn8n5R8_KJ5W-pTm_fRCA,39
37
- kea2_python-0.3.0.dist-info/top_level.txt,sha256=TsgNH4PQoNOVhegpO7AcjutMVWp6Z4KDL1pBH9FnMmk,5
38
- kea2_python-0.3.0.dist-info/RECORD,,
31
+ kea2/templates/bug_report_template.html,sha256=XoLAqiPfk5asvSRhhI5nCity8Z77rxMBGT2_8rFrJP4,151729
32
+ kea2/templates/merged_bug_report_template.html,sha256=awhMSURsP6Gpw0YHTs3xWZMQO7dhi0yM3H2JYHQ5_zA,135668
33
+ kea2_python-0.3.2.dist-info/licenses/LICENSE,sha256=nM9PPjcsXVo5SzNsjRqWgA-gdJlwqZZcRDSC6Qf6bVE,2034
34
+ kea2_python-0.3.2.dist-info/METADATA,sha256=dD3gescsC5Q-R8OZYFfvbiYBDhvJefFXzopffB2EPVI,18498
35
+ kea2_python-0.3.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
36
+ kea2_python-0.3.2.dist-info/entry_points.txt,sha256=mFX06TyxXiUAJQ6JZn8QHzfn8n5R8_KJ5W-pTm_fRCA,39
37
+ kea2_python-0.3.2.dist-info/top_level.txt,sha256=TsgNH4PQoNOVhegpO7AcjutMVWp6Z4KDL1pBH9FnMmk,5
38
+ kea2_python-0.3.2.dist-info/RECORD,,