xtn-tools-pro 1.0.0.0.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # 说明:
5
+ # __init__
6
+ # History:
7
+ # Date Author Version Modification
8
+ # --------------------------------------------------------------------------------------------------
9
+ # 2024/4/17 xiatn V00.01.000 新建
10
+ # --------------------------------------------------------------------------------------------------
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # 说明:
5
+ # MongoDBPro
6
+ # History:
7
+ # Date Author Version Modification
8
+ # --------------------------------------------------------------------------------------------------
9
+ # 2024/4/17 xiatn V00.01.000 新建
10
+ # --------------------------------------------------------------------------------------------------
11
+ from xtn_tools_pro.tools_time import *
12
+ from urllib import parse
13
+ from pymongo import MongoClient as _MongoClient
14
+ from pymongo.database import Database as _Database
15
+ from typing import List, Dict, Optional
16
+ from pymongo.collection import Collection as _Collection
17
+ from pymongo.errors import DuplicateKeyError, BulkWriteError
18
+
19
+
20
+ class MongoDBPro:
21
+ def __init__(self, ip=None, port=None, db=None, user_name=None, user_pass=None, url=None, **kwargs):
22
+ if url:
23
+ self.client = _MongoClient(url, **kwargs)
24
+ else:
25
+ self.client = _MongoClient(host=ip,
26
+ port=port,
27
+ username=user_name,
28
+ password=user_pass,
29
+ authSource=db)
30
+
31
+ self.db = self.get_database(db)
32
+
33
+ @classmethod
34
+ def from_url(cls, url, **kwargs):
35
+ url_parsed = parse.urlparse(url)
36
+ # 获取 URL的协议
37
+ db_type = url_parsed.scheme.strip()
38
+ if db_type != "mongodb":
39
+ raise Exception(
40
+ "url error, expect mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]], but get {}".format(
41
+ url
42
+ ))
43
+ return cls(url=url, **kwargs)
44
+
45
+ def get_database(self, database, **kwargs) -> _Database:
46
+ """
47
+ 根据db名获取数据库对象
48
+ """
49
+ return self.client.get_database(database, **kwargs)
50
+
51
+ def get_collection(self, coll_name, **kwargs) -> _Collection:
52
+ """
53
+ 根据集合名获取集合对象
54
+ """
55
+ return self.db.get_collection(coll_name, **kwargs)
56
+
57
+ def run_command(self, command: Dict):
58
+ """
59
+ 参考文档 https://www.geek-book.com/src/docs/mongodb/mongodb/docs.mongodb.com/manual/reference/command/index.html
60
+ """
61
+ return self.db.command(command)
62
+
63
+ def find(self, coll_name: str, condition: Optional[Dict] = None,
64
+ limit: int = 0, **kwargs) -> List[Dict]:
65
+ """
66
+ find
67
+ coll_name:集合名称
68
+ condition:查询条件 例如:{"name": "John"}、{"_id": "xxxxx"}
69
+ """
70
+ condition = {} if condition is None else condition
71
+ command = {"find": coll_name, "filter": condition, "limit": limit}
72
+ command.update(kwargs)
73
+ result = self.run_command(command)
74
+ cursor = result["cursor"]
75
+ cursor_id = cursor["id"]
76
+ while True:
77
+ for document in cursor.get("nextBatch", cursor.get("firstBatch", [])):
78
+ # 处理数据
79
+ yield document
80
+ if cursor_id == 0:
81
+ # 游标已经完全遍历,没有剩余的结果可供获取
82
+ # 游标的生命周期已经结束,例如在查询会话结束后。
83
+ # 游标被显式地关闭,例如使用 db.killCursor() 命令关闭游标。
84
+ break
85
+ result = self.run_command(
86
+ {
87
+ "getMore": cursor_id, # 类似于mongo命令行中的it命令,通过索引id用于获取下一批结果
88
+ "collection": coll_name,
89
+ "batchSize": kwargs.get("batchSize", 100),
90
+ }
91
+ )
92
+ # 覆盖原来的参数
93
+ cursor = result["cursor"]
94
+ cursor_id = cursor["id"]
95
+ # print("下一批获取")
96
+
97
+ def add_data_one(self, coll_name: str, data: Dict, insert_ignore=False,
98
+ is_add_create_time=False,
99
+ is_add_create_time_field_name="create_dt"):
100
+ """
101
+ 添加单条数据
102
+ coll_name: 集合名
103
+ data: 单条数据
104
+ insert_ignore: 索引冲突是否忽略 默认False
105
+ is_add_create_time: 是否在数据中添加一个创建数据10时间戳字段 默认False不创建
106
+ is_add_create_time_field_name: 自定义创建数据时间戳字段名:默认:create_dt
107
+ Returns: 插入成功的行数
108
+ """
109
+ if is_add_create_time:
110
+ data[is_add_create_time_field_name] = get_time_now_timestamp(is_time_10=True)
111
+ collection = self.get_collection(coll_name)
112
+ try:
113
+ collection.insert_one(data)
114
+ except DuplicateKeyError as e:
115
+ if not insert_ignore:
116
+ raise e
117
+ return 0
118
+ return 1
119
+
120
+ def find_id_is_exist(self, coll_name, _id):
121
+ """
122
+ 根据id查询id是否存在
123
+ :param _id:id
124
+ :return: 存在返回True 否则False
125
+ """
126
+ condition = {"_id": _id}
127
+ status = list(self.find(coll_name, condition))
128
+ if status:
129
+ return True
130
+ return False
131
+
132
+
133
+ if __name__ == '__main__':
134
+ pass
135
+ # mongo_db = MongoDBPro("127.0.0.1", 27017, "spider_pro")
136
+ # # mongo_db.add_data_one("test", {"_id": "1", "data": "aaa"})
137
+ # print(mongo_db.find_id_is_exist("test", "1"))
138
+ # print(mongo_db.find_id_is_exist("test", "11"))
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # 说明:
5
+ # 程序说明xxxxxxxxxxxxxxxxxxx
6
+ # History:
7
+ # Date Author Version Modification
8
+ # --------------------------------------------------------------------------------------------------
9
+ # 2024/4/18 xiatn V00.01.000 新建
10
+ # --------------------------------------------------------------------------------------------------
11
+ import redis, time
12
+
13
+
14
+ class RedisDBPro:
15
+ def __init__(self, ip=None, port=None, db=None,
16
+ user_pass=None, url=None, decode_responses=True, max_connections=1000, **kwargs):
17
+
18
+ self._ip = ip
19
+ self._port = port
20
+ self._db = db
21
+ self._user_pass = user_pass
22
+ self._url = url
23
+ self._decode_responses = decode_responses # 自动解码返回的字节串
24
+ self._max_connections = max_connections # 同一个redis对象使用的并发数(连接池的最大连接数),超过这个数量会抛出redis.ConnectionError
25
+ self._kwargs = kwargs
26
+ # 连接
27
+ self.__redis = None
28
+ self.get_connect()
29
+
30
+ @classmethod
31
+ def from_url(cls, url):
32
+ """
33
+
34
+ Args:
35
+ url: redis://[[username]:[password]]@[host]:[port]/[db]
36
+
37
+ Returns:
38
+
39
+ """
40
+ return cls(url=url)
41
+
42
+ @property
43
+ def _redis(self):
44
+ try:
45
+ if not self.__redis.ping():
46
+ raise ConnectionError("unable to connect to redis")
47
+ except:
48
+ self._reconnect()
49
+
50
+ return self.__redis
51
+
52
+ @_redis.setter
53
+ def _redis(self, val):
54
+ self.__redis = val
55
+
56
+ def _reconnect(self):
57
+ # 检测连接状态, 当数据库重启或设置 timeout 导致断开连接时自动重连
58
+ retry_count = 0
59
+ while True:
60
+ try:
61
+ retry_count += 1
62
+ print("redis 连接断开, 重新连接 {retry_count}".format(retry_count=retry_count))
63
+ if self.get_connect():
64
+ print("redis 连接成功")
65
+ return True
66
+ except (ConnectionError, TimeoutError) as e:
67
+ print("连接失败 e: {e}".format(e=e))
68
+ time.sleep(2)
69
+
70
+ def get_connect(self):
71
+ # 数据库连接
72
+ try:
73
+ if not self._url:
74
+ self._redis = redis.StrictRedis(
75
+ host=self._ip,
76
+ port=self._port,
77
+ db=self._db,
78
+ password=self._user_pass,
79
+ decode_responses=self._decode_responses,
80
+ max_connections=self._max_connections,
81
+ **self._kwargs,
82
+ )
83
+ else:
84
+ # url连接
85
+ self._redis = redis.StrictRedis.from_url(
86
+ self._url, decode_responses=self._decode_responses, **self._kwargs
87
+ )
88
+
89
+ except Exception as e:
90
+ raise e
91
+
92
+ return self.__redis.ping()
93
+
94
+ def sadd(self, table, values):
95
+ """
96
+ 使用无序set集合存储数据, 去重
97
+ table:集合名
98
+ values: 值; 支持list 或 单个值
99
+ ---------
100
+ @result: 若库中存在 返回0,否则入库,返回1。 批量添加返回None
101
+ """
102
+ if isinstance(values, list):
103
+ pipe = self._redis.pipeline() # 创建一个pipeline对象,可以添加多个操作,最后execute提交
104
+ pipe.multi()
105
+ for value in values:
106
+ pipe.sadd(table, value)
107
+ result = pipe.execute()
108
+ # print(result) # [1, 1, 0, 0, 0, 0, 0]
109
+ return result.count(1)
110
+ else:
111
+ return self._redis.sadd(table, values)
112
+
113
+
114
+ if __name__ == '__main__':
115
+ pass
116
+ # r = RedisDBPro(ip="127.0.0.1", port=6379, db=0, user_pass="xtn-kk")
117
+ # status = r.sadd("test_redis_pro", [1, 2, 3, 4, 5, "6", "7"])
118
+ # print(status)
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # 说明:
5
+ # 程序说明xxxxxxxxxxxxxxxxxxx
6
+ # History:
7
+ # Date Author Version Modification
8
+ # --------------------------------------------------------------------------------------------------
9
+ # 2024/4/18 xiatn V00.01.000 新建
10
+ # --------------------------------------------------------------------------------------------------
xtn_tools_pro/tools.py ADDED
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # 说明:
5
+ # tools
6
+ # History:
7
+ # Date Author Version Modification
8
+ # --------------------------------------------------------------------------------------------------
9
+ # 2024/4/17 xiatn V00.01.000 新建
10
+ # --------------------------------------------------------------------------------------------------
11
+ import hashlib, json, math
12
+ from urllib.parse import urlencode
13
+
14
+
15
+ def get_md5_32(s, is_upper=False):
16
+ """
17
+ 获取文本的md5值 32位
18
+ :param s: 文本
19
+ :param is_upper: 是否转大写 默认False
20
+ :return:
21
+ """
22
+ # s.encode()#变成bytes类型才能加密
23
+ m = hashlib.md5(s.encode()) # 长度是32
24
+ if is_upper:
25
+ return m.hexdigest().upper()
26
+ return m.hexdigest()
27
+
28
+
29
+ def get_md5_16(s, is_upper=False):
30
+ """
31
+ 获取文本的md5值 16位
32
+ :param s: 文本
33
+ :param is_upper: 是否转大写 默认False
34
+ :return:
35
+ """
36
+ result = get_md5_32(s, is_upper)
37
+ return result[8:24]
38
+
39
+
40
+ def get_file_md5_32(file_path):
41
+ """
42
+ 获取文件md5值
43
+ :param file_path: 文件路径
44
+ :return:
45
+ """
46
+ with open(file_path, 'rb') as file:
47
+ data = file.read()
48
+ md5_hash = hashlib.md5(data).hexdigest()
49
+ return md5_hash
50
+
51
+
52
+ def get_file_md5_16(file_path):
53
+ """
54
+ 获取文件md5值
55
+ :param file_path: 文件路径
56
+ :return:
57
+ """
58
+ result = get_file_md5_32(file_path)
59
+ return result[8:24]
60
+
61
+
62
+
63
+ def get_str_to_json(str_json):
64
+ """
65
+ 字符串类型的json格式 转 json
66
+ :param str_json: 字符串json
67
+ :return:
68
+ """
69
+ try:
70
+ new_str_json = str_json.replace("'", '"'). \
71
+ replace("None", "null").replace("True", "true"). \
72
+ replace("False", "false")
73
+ return json.loads(new_str_json)
74
+ except Exception as e:
75
+ return {}
76
+
77
+
78
+ def get_build_url_with_params(url, params):
79
+ """
80
+ 传入url和params拼接完整的url ->效果 https://wwww.xxxx.com/?xxx1=1&xxx2=2
81
+ :param url:
82
+ :param params:
83
+ :return:
84
+ """
85
+ encoded_params = urlencode(params)
86
+ full_url = url + "?" + encoded_params
87
+ return full_url
88
+
89
+
90
+ def get_calculate_total_page(total, limit):
91
+ """
92
+ 根据total和limit计算出一共有多少页
93
+ :param total:
94
+ :param limit:
95
+ :return:
96
+ """
97
+ if limit <= 0:
98
+ return 0
99
+ # 根据总条数和limit计算总页数
100
+ total_pages = math.ceil(total / limit)
101
+ return total_pages
102
+
103
+
104
+ if __name__ == '__main__':
105
+ pass
106
+
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # 说明:
5
+ # 程序说明xxxxxxxxxxxxxxxxxxx
6
+ # History:
7
+ # Date Author Version Modification
8
+ # --------------------------------------------------------------------------------------------------
9
+ # 2024/4/18 xiatn V00.01.000 新建
10
+ # --------------------------------------------------------------------------------------------------
11
+ import time, datetime
12
+
13
+
14
+ def get_time_now_timestamp(is_time_10=False, is_time_13=False):
15
+ """
16
+ 获取当前时间戳
17
+ :param is_time_10: 是否需要处理为10位的时间戳,默认不处理
18
+ :param is_time_13: 是否需要处理为13位的时间戳,默认不处理
19
+ :return:
20
+ """
21
+
22
+ if is_time_10:
23
+ val = int(time.time())
24
+ elif is_time_13:
25
+ val = int(time.time() * 1000)
26
+ else:
27
+ val = time.time()
28
+ return val
29
+
30
+
31
+ def get_time_now_day0_timestamp(is_time_13=False):
32
+ """
33
+ 获取当天0点时间戳
34
+ :param is_time_13: 是否需要处理为13位的时间戳,默认不处理并且返回10位时间戳
35
+ :return:
36
+ """
37
+ val = time.mktime(datetime.date.today().timetuple())
38
+ if is_time_13:
39
+ return int(val * 1000)
40
+ else:
41
+ return int(val)
42
+
43
+
44
+ def get_time_now_day59_timestamp(is_time_13=False):
45
+ """
46
+ 获取当天23:59:59点时间戳
47
+ :param is_time_13: 是否需要处理为13位的时间戳,默认不处理并且返回10位时间戳
48
+ :return:
49
+ """
50
+ # 获取当前日期时间
51
+ now = datetime.datetime.now()
52
+ # 设置小时、分钟、秒为 23:59:59
53
+ last_second = now.replace(hour=23, minute=59, second=59)
54
+ # 转换为时间戳
55
+ timestamp = time.mktime(last_second.timetuple())
56
+ # 转换为整数类型
57
+ if is_time_13:
58
+ return get_time_10_to_13_timestamp(timestamp)
59
+ else:
60
+ return int(timestamp)
61
+
62
+
63
+ def get_time_x_day_timestamp(x, is_time_13=False):
64
+ """
65
+ 获取x天的0点的时间戳
66
+ :param x: 0:当天; 1:1天后; -1:一天前
67
+ :param is_time_13: 是否需要处理为13位的时间戳,默认不处理并且返回10位时间戳
68
+ :return:
69
+ """
70
+ if x == 0:
71
+ date_string = datetime.datetime.now().strftime("%Y-%m-%d") # 当天日期
72
+ elif x > 0:
73
+ future_date = datetime.datetime.now() + datetime.timedelta(days=x)
74
+ date_string = future_date.strftime("%Y-%m-%d") # x天后的日期
75
+ else:
76
+ past_date = datetime.datetime.now() - datetime.timedelta(days=abs(x))
77
+ date_string = past_date.strftime("%Y-%m-%d") # x天前的日期
78
+
79
+ timestamp = get_time_datestr_to_timestamp(date_string=date_string, is_time_13=is_time_13)
80
+ return timestamp
81
+
82
+
83
+ def get_time_datestr_to_timestamp(date_string, date_format="%Y-%m-%d", is_time_13=False):
84
+ """
85
+ 根据日期格式转换为时间戳,date_string和date_format需要配合,自行传参修改,这里是以%Y-%m-%d为格式也就是2024-04-18
86
+ :param date_string: 字符串类型的日期格式 例如:2024-04-18
87
+ :param date_format: 时间格式
88
+ :param is_time_13: 是否需要处理为13位的时间戳,默认不处理并且返回10位时间戳
89
+ :return:
90
+ """
91
+ date_obj = datetime.datetime.strptime(date_string, date_format)
92
+ timestamp = date_obj.timestamp()
93
+ if is_time_13:
94
+ return get_time_10_to_13_timestamp(timestamp)
95
+ else:
96
+ return int(timestamp)
97
+
98
+
99
+ def get_time_10_to_13_timestamp(timestamp):
100
+ """
101
+ 10位时间戳转13位时间戳
102
+ :param timestamp:
103
+ :return:
104
+ """
105
+ val = int(timestamp)
106
+ if len(str(val)) == 10:
107
+ return int(val * 1000)
108
+ return val
109
+
110
+
111
+ def get_time_13_to_10_timestamp(timestamp):
112
+ """
113
+ 13位时间戳转10位时间戳
114
+ :param timestamp:
115
+ :return:
116
+ """
117
+ val = int(timestamp)
118
+ if len(str(val)) == 13:
119
+ return int(val // 1000)
120
+ return val
121
+
122
+
123
+ def get_time_timestamp_to_datestr(format='%Y-%m-%d %H:%M:%S', now_time=0):
124
+ """
125
+ 根据时间戳转换为日期格式,兼容10位时间戳和13位时间戳
126
+ :param format: 日期格式,常用:%Y-%m-%d %H:%M:%S、%Y-%m-%d、%Y/%m/%d、%H:%M:%S ...
127
+ :param now_time: 时间戳,默认0表示当前时间戳
128
+ :return:
129
+ """
130
+ # 根据格式获取当前转换好的时间
131
+ if not now_time:
132
+ now_time = get_time_now_timestamp()
133
+ now_time = get_time_13_to_10_timestamp(now_time)
134
+ val = time.strftime(format, time.localtime(now_time))
135
+ return val
136
+
137
+
138
+ if __name__ == '__main__':
139
+ pass
140
+ print(get_time_timestamp_to_datestr())
141
+ print(get_time_timestamp_to_datestr(format="%H:%M:%S", now_time=get_time_now_timestamp(is_time_10=True)))
142
+ print(get_time_timestamp_to_datestr(now_time=get_time_now_timestamp(is_time_13=True)))
File without changes
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.1
2
+ Name: xtn-tools-pro
3
+ Version: 1.0.0.0.0
4
+ Summary: xtn 开发工具
5
+ Author: xtn
6
+ Author-email: czw011122@163.com
7
+ Classifier: Programming Language :: Python :: 3
8
+ Requires-Python: >=3
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Requires-Dist: pymongo
12
+ Requires-Dist: redis
13
+
14
+ xtnkk-tools
@@ -0,0 +1,24 @@
1
+ xtn_tools_pro/__init__.py,sha256=26Tf9j2wj88M1Ldg3b1DJ40KyGgN9ZmQdBLuV453388,395
2
+ xtn_tools_pro/tools.py,sha256=KYoTds_c7XZBL9yLeoKksHz39QPh02DNQupRKJWx_II,2626
3
+ xtn_tools_pro/tools_time.py,sha256=n4-T2tNSHnsh-X89IbjahCmoiDcmjZTKJlWyqGOmJQY,4877
4
+ xtn_tools_pro/db/MongoDB.py,sha256=_GiX1MHNl9CtI-uLDgY_NmMSvRJei-mtKq3Hhe6ly1E,5567
5
+ xtn_tools_pro/db/RedisDB.py,sha256=qMffCNIHa3o7KD_yVQlsj3OupsNXMsDPRi03migwSu0,4003
6
+ xtn_tools_pro/db/__init__.py,sha256=Zg91UWS02TO0Ba_0AY56s0oabRy93xLNFkpIIL_6mMM,416
7
+ xtnkk_tools/MongoDB.py,sha256=2mwln6JPfu5N1N8Hbh6KvN6sED-KPTrOteCBHVFjvwM,5497
8
+ xtnkk_tools/__init__.py,sha256=26Tf9j2wj88M1Ldg3b1DJ40KyGgN9ZmQdBLuV453388,395
9
+ xtnkk_tools/tools.py,sha256=KYoTds_c7XZBL9yLeoKksHz39QPh02DNQupRKJWx_II,2626
10
+ xtnkk_tools/tools_time.py,sha256=n4-T2tNSHnsh-X89IbjahCmoiDcmjZTKJlWyqGOmJQY,4877
11
+ xtnkk_tools/update.py,sha256=VygnKO9dXo02JyUEkpbJoBE6BceYARZEn-O1i6AO6E0,911
12
+ xtnkk_tools/db/MongoDB.py,sha256=_GiX1MHNl9CtI-uLDgY_NmMSvRJei-mtKq3Hhe6ly1E,5567
13
+ xtnkk_tools/db/__init__.py,sha256=Zg91UWS02TO0Ba_0AY56s0oabRy93xLNFkpIIL_6mMM,416
14
+ xtnkk_tools_pro/__init__.py,sha256=26Tf9j2wj88M1Ldg3b1DJ40KyGgN9ZmQdBLuV453388,395
15
+ xtnkk_tools_pro/tools.py,sha256=KYoTds_c7XZBL9yLeoKksHz39QPh02DNQupRKJWx_II,2626
16
+ xtnkk_tools_pro/tools_time.py,sha256=n4-T2tNSHnsh-X89IbjahCmoiDcmjZTKJlWyqGOmJQY,4877
17
+ xtnkk_tools_pro/db/MongoDB.py,sha256=_GiX1MHNl9CtI-uLDgY_NmMSvRJei-mtKq3Hhe6ly1E,5567
18
+ xtnkk_tools_pro/db/RedisDB.py,sha256=qMffCNIHa3o7KD_yVQlsj3OupsNXMsDPRi03migwSu0,4003
19
+ xtnkk_tools_pro/db/__init__.py,sha256=Zg91UWS02TO0Ba_0AY56s0oabRy93xLNFkpIIL_6mMM,416
20
+ xtn_tools_pro-1.0.0.0.0.dist-info/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
+ xtn_tools_pro-1.0.0.0.0.dist-info/METADATA,sha256=2PylBr2rHM7pYarZGJm1vBeF6caFfG1A-zY-tXP8rPg,333
22
+ xtn_tools_pro-1.0.0.0.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
23
+ xtn_tools_pro-1.0.0.0.0.dist-info/top_level.txt,sha256=jyB3FLDEr8zE1U7wHczTgIbvUpALhR-ULF7RVEO7O2U,14
24
+ xtn_tools_pro-1.0.0.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: bdist_wheel (0.42.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ xtn_tools_pro