gomyck-tools 1.3.4__py3-none-any.whl → 1.3.6__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.
@@ -0,0 +1,201 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: UTF-8 -*-
3
+ __author__ = 'haoyang'
4
+ __date__ = '2025/5/19 16:45'
5
+
6
+ import re
7
+
8
+ from ctools import cjson
9
+
10
+
11
+ def extract_json_from_text(text):
12
+ """
13
+ 从混杂文本中提取第一个完整的 JSON 对象
14
+ """
15
+ import json
16
+
17
+ # 方法1:尝试直接解析
18
+ try:
19
+ return json.loads(text)
20
+ except json.JSONDecodeError:
21
+ pass
22
+
23
+ # 方法2:字符级括号匹配提取 JSON
24
+ start = None
25
+ brace_count = 0
26
+ for i, char in enumerate(text):
27
+ if char == '{':
28
+ if start is None:
29
+ start = i
30
+ brace_count += 1
31
+ elif char == '}':
32
+ brace_count -= 1
33
+ if brace_count == 0 and start is not None:
34
+ json_candidate = text[start:i + 1]
35
+ try:
36
+ return json.loads(json_candidate)
37
+ except json.JSONDecodeError:
38
+ start = None # 重置继续寻找下一个可能的 JSON
39
+
40
+ # 方法3:尝试 JSONP 格式
41
+ match = re.search(r'\((\{[\s\S]*\})\)', text)
42
+ if match:
43
+ try:
44
+ return json.loads(match.group(1))
45
+ except json.JSONDecodeError:
46
+ pass
47
+
48
+ return None
49
+
50
+ if __name__ == '__main__':
51
+ xx = """
52
+ <think>
53
+
54
+ </think>
55
+
56
+ {
57
+ "sjbs": {
58
+ "xsbt": "东都公园业主集体信访事件",
59
+ "sjbh": "202406031234",
60
+ "jjcd": ["黄"]
61
+ },
62
+ "skys": {
63
+ "zxsj": [
64
+ {
65
+ "jqsj": "2024-06-03 09:00",
66
+ "sjms": "6月3日",
67
+ "sjlx": ["计划时间"]
68
+ }
69
+ ],
70
+ "zxdd": [
71
+ {
72
+ "bzdz": "黑龙江省哈尔滨市信访局",
73
+ "cslx": ["政府机关"]
74
+ }
75
+ ]
76
+ },
77
+ "ssqt": {
78
+ "qtms": ["哈尔滨市道外区东都公园业主"],
79
+ "qtgm": ["约5人以上,可能发展至群体性事件"],
80
+ "qtbq": ["房地产纠纷", "历史遗留问题"],
81
+ "zztz": ["有核心组织"]
82
+ },
83
+ "ryqd": [
84
+ {
85
+ "xm": ["杨开亮"],
86
+ "sfzh": ["2301251968101335**"],
87
+ "js": ["组织者"],
88
+ "hjd": ["哈尔滨市宾县满井镇永宁村崔海屯"],
89
+ "jzd": ["团结镇东都公元一区五栋二单元603"],
90
+ "lxdh": ["139366789**"],
91
+ "rybq": ["重点人"],
92
+ "wlzh": {
93
+ "wx": [],
94
+ "qq": []
95
+ },
96
+ "gjxx": [
97
+ {
98
+ "sj": ["2024-05-28 20:26"],
99
+ "dd": ["网络群聊"],
100
+ "xw": ["组织动员"]
101
+ }
102
+ ]
103
+ },
104
+ {
105
+ "xm": ["孙凤玲"],
106
+ "sfzh": ["2301041955121712**"],
107
+ "js": ["积极参与者"],
108
+ "hjd": ["哈尔滨市道外区迎新街好民居滨港水岸D15栋1单元14层4号"],
109
+ "jzd": ["道外区陶瓷小区D15-1-1404"],
110
+ "lxdh": ["17758887348"],
111
+ "rybq": [],
112
+ "wlzh": {
113
+ "wx": [],
114
+ "qq": []
115
+ },
116
+ "gjxx": [
117
+ {
118
+ "sj": ["2024-05-28 19:00"],
119
+ "dd": ["网络群聊"],
120
+ "xw": ["响应组织"]
121
+ }
122
+ ]
123
+ },
124
+ {
125
+ "xm": ["高秀艳"],
126
+ "sfzh": ["2323261982060762**"],
127
+ "js": ["积极参与者"],
128
+ "hjd": ["绥化市青冈县劳动乡北斗村丛喜云屯"],
129
+ "jzd": ["哈尔滨市道外区团结镇森桐木业"],
130
+ "lxdh": ["15846349146"],
131
+ "rybq": [],
132
+ "wlzh": {
133
+ "wx": [],
134
+ "qq": []
135
+ },
136
+ "gjxx": [
137
+ {
138
+ "sj": ["2024-05-28 20:00"],
139
+ "dd": ["网络群聊"],
140
+ "xw": ["响应组织"]
141
+ }
142
+ ]
143
+ },
144
+ {
145
+ "xm": ["高振凤"],
146
+ "sfzh": ["2323031974103046**"],
147
+ "js": ["一般参与者"],
148
+ "hjd": ["绥化市肇东市东发乡夕阳村郭家屯"],
149
+ "jzd": ["团结镇团结镇东都公园一区七栋一单元101"],
150
+ "lxdh": ["18004659805"],
151
+ "rybq": [],
152
+ "wlzh": {
153
+ "wx": [],
154
+ "qq": []
155
+ },
156
+ "gjxx": [
157
+ {
158
+ "sj": ["2024-05-28 19:30"],
159
+ "dd": ["网络群聊"],
160
+ "xw": ["响应组织"]
161
+ }
162
+ ]
163
+ },
164
+ {
165
+ "xm": ["陈立军"],
166
+ "sfzh": ["2301251980031907**"],
167
+ "js": ["组织者", "群主"],
168
+ "hjd": ["哈尔滨市宾县宾西镇一委六组"],
169
+ "jzd": [],
170
+ "lxdh": ["15776806667"],
171
+ "rybq": ["重点人"],
172
+ "wlzh": {
173
+ "wx": [],
174
+ "qq": []
175
+ },
176
+ "gjxx": [
177
+ {
178
+ "sj": ["2024-05-28 19:00"],
179
+ "dd": ["网络群聊"],
180
+ "xw": ["组织动员"]
181
+ }
182
+ ]
183
+ }
184
+ ],
185
+ "sjtz": {
186
+ "xwlx": ["集体信访", "网络串联"],
187
+ "sqnr": ["要求政府解决房产证办理问题,明确责任主体并推动政策落实"],
188
+ "dktz": [],
189
+ "zjly": ["自筹"]
190
+ },
191
+ "czjy": {
192
+ "zrdw": ["哈尔滨市公安局道外分局", "信访维稳专班"],
193
+ "yjcs": ["提前约谈重点人员", "加强网络群组监控", "部署现场警力"]
194
+ }
195
+ }
196
+ 20250603142141-INFO-8399134464-web_log(log:214) 127.0.0.1 [03/Jun/2025:14:18:39 +0800] "POST /chat/completion HTTP/1.1" 200 3670 "-" "python-requests/2.32.3"
197
+
198
+
199
+ """
200
+
201
+ print((cjson.dumps(extract_json_from_text(xx))))
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: UTF-8 -*-
3
+ __author__ = 'haoyang'
4
+ __date__ = '2025/6/3 14:30'
5
+
6
+ import re
7
+
8
+
9
+ def remove_think_blocks(text: str) -> str:
10
+ cleaned_text = re.sub(r"<think>.*?</think>", "", text, flags=re.DOTALL)
11
+ return cleaned_text.strip()
@@ -0,0 +1,32 @@
1
+ import json
2
+ import xml.etree.ElementTree as ET
3
+ import html
4
+
5
+
6
+ def parse_tool_use(xml_string):
7
+ tool_name = ''
8
+ try:
9
+ root = ET.fromstring(xml_string.strip())
10
+ if root.tag != "tool_use": raise ValueError("根标签必须是 <tool_use>")
11
+ tool_name = root.find("name").text.strip()
12
+ arguments_text = root.find("arguments").text.strip()
13
+ arguments_text = html.unescape(arguments_text)
14
+ arguments = json.loads(arguments_text)
15
+ return {
16
+ "tool": tool_name,
17
+ "arguments": arguments
18
+ }
19
+ except Exception as e:
20
+ raise ValueError(f"tool_use_{tool_name} 解析失败: {e}")
21
+
22
+ # 测试
23
+ if __name__ == '__main__':
24
+ xml_input = """
25
+ <tool_use>
26
+ <name>set</name>
27
+ <arguments>{"key": "weather_harbin", "value": "{\\"city\\":\\"哈尔滨市\\",\\"forecasts\\":[{\\"date\\":\\"2025-05-27\\",\\"week\\":\\"2\\",\\"dayweather\\":\\"晴\\",\\"nightweather\\":\\"晴\\",\\"daytemp\\":29,\\"nighttemp\\":15,\\"daywind\\":\\"南\\",\\"nightwind\\":\\"南\\",\\"daypower\\":1,\\"nightpower\\":3},{\\"date\\":\\"2025-05-28\\",\\"week\\":"3", \\"dayweather\\":"晴", \\"nightweather\\":"晴", \\"daytemp\\":"30", \\"nighttemp\\":"17"}]}"}</arguments>
28
+ </tool_use>
29
+ """
30
+ result = parse_tool_use(xml_input)
31
+ print("\n【结果】")
32
+ print(json.dumps(result, ensure_ascii=False, indent=2))
@@ -0,0 +1,13 @@
1
+ import re
2
+
3
+ def extract_all_xml_blocks(text):
4
+ return re.findall(r"<tool_use>.*?</tool_use>", text, re.DOTALL)
5
+ text = """
6
+ 一些内容...
7
+ 123
8
+ """
9
+
10
+ results = extract_all_xml_blocks(text)
11
+ print(results)
12
+ for xml_block in results:
13
+ print(xml_block)
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: UTF-8 -*-
3
+ """A lightweight async HTTP server based on aiohttp."""
4
+
5
+ __author__ = 'haoyang'
6
+ __date__ = '2025/5/30 09:54'
7
+
8
+ import asyncio
9
+ import sys
10
+ from pathlib import Path
11
+ from typing import Optional, Dict, Any
12
+
13
+ from aiohttp import web
14
+
15
+ from ctools import sys_info
16
+
17
+ DEFAULT_PORT = 8888
18
+
19
+ class AioHttpServer:
20
+ def __init__(self, port: int = DEFAULT_PORT, app: Optional[web.Application] = None, routes: Optional[web.RouteTableDef] = None, async_func = None):
21
+ """
22
+ Initialize the HTTP server.
23
+
24
+ Args:
25
+ port: Port number to listen on
26
+ app: Optional existing aiohttp Application instance
27
+ """
28
+ self.app = app or web.Application()
29
+ self.port = port
30
+ self.index_root = Path('./')
31
+ self.index_filename = 'index.html'
32
+ self.is_tpl = False
33
+ self.template_args: Dict[str, Any] = {}
34
+ self.redirect_url: Optional[str] = None
35
+ self.static_root = Path('./static')
36
+ self.download_root = Path('./download')
37
+ self.routes = routes
38
+ self.async_func = async_func
39
+
40
+ # Register routes
41
+ self.app.add_routes([
42
+ web.get('/', self.handle_index),
43
+ web.get('/index', self.handle_index),
44
+ web.get('/static/{filepath:.*}', self.handle_static),
45
+ web.get('/download/{filepath:.*}', self.handle_download)
46
+ ])
47
+ if self.routes:
48
+ self.app.add_routes(self.routes)
49
+
50
+ async def handle_index(self, request: web.Request) -> web.StreamResponse:
51
+ """Handle requests to the index page."""
52
+ if self.redirect_url:
53
+ return web.HTTPFound(self.redirect_url)
54
+
55
+ index_path = self.index_root / self.index_filename
56
+
57
+ if not index_path.exists():
58
+ return web.HTTPNotFound()
59
+
60
+ if self.is_tpl:
61
+ # If using templates, you might want to use a template engine here
62
+ return web.FileResponse(
63
+ index_path,
64
+ headers={'Content-Type': 'text/html'}
65
+ )
66
+ return web.FileResponse(index_path)
67
+
68
+ async def handle_static(self, request: web.Request) -> web.StreamResponse:
69
+ """Handle static file requests."""
70
+ filepath = Path(request.match_info['filepath'])
71
+ full_path = self.static_root / filepath
72
+
73
+ if not full_path.exists():
74
+ return web.HTTPNotFound()
75
+
76
+ return web.FileResponse(full_path)
77
+
78
+ async def handle_download(self, request: web.Request) -> web.StreamResponse:
79
+ """Handle file download requests."""
80
+ filepath = Path(request.match_info['filepath'])
81
+ full_path = self.download_root / filepath
82
+
83
+ if not full_path.exists():
84
+ return web.HTTPNotFound()
85
+
86
+ return web.FileResponse(
87
+ full_path,
88
+ headers={'Content-Disposition': f'attachment; filename="{filepath.name}"'}
89
+ )
90
+
91
+ def set_index(
92
+ self,
93
+ filename: str = 'index.html',
94
+ root: str = './',
95
+ is_tpl: bool = False,
96
+ redirect_url: Optional[str] = None,
97
+ **kwargs: Any
98
+ ) -> None:
99
+ """
100
+ Configure index page settings.
101
+
102
+ Args:
103
+ filename: Name of the index file
104
+ root: Root directory for index file
105
+ is_tpl: Whether the file is a template
106
+ redirect_url: URL to redirect to instead of serving index
107
+ kwargs: Additional template arguments
108
+ """
109
+ self.index_root = Path(root)
110
+ self.index_filename = filename
111
+ self.is_tpl = is_tpl
112
+ self.redirect_url = redirect_url
113
+ self.template_args = kwargs
114
+
115
+ def set_static(self, root: str = './static') -> None:
116
+ """Set the root directory for static files."""
117
+ self.static_root = Path(root)
118
+
119
+ def set_download(self, root: str = './download') -> None:
120
+ """Set the root directory for downloadable files."""
121
+ self.download_root = Path(root)
122
+
123
+ async def run(self) -> None:
124
+ """Run the server."""
125
+ if self.async_func:
126
+ await self.async_func()
127
+ print(
128
+ 'Server running at:\n'
129
+ f'\tLocal: http://localhost:{self.port}\n'
130
+ f'\tNetwork: http://{sys_info.get_local_ipv4()}:{self.port}',
131
+ file=sys.stderr
132
+ )
133
+ await web._run_app(
134
+ self.app,
135
+ port=self.port,
136
+ host='0.0.0.0'
137
+ )
138
+
139
+
140
+ def init_routes() -> web.RouteTableDef:
141
+ return web.RouteTableDef()
142
+
143
+ def init_server(routes: Optional[web.RouteTableDef] = None, app: Optional[web.Application] = None, port: int = DEFAULT_PORT, async_func = None) -> AioHttpServer:
144
+ """Initialize and return a new AioHttpServer instance."""
145
+ return AioHttpServer(port=port, app=app, routes=routes, async_func=async_func)
ctools/bottle_web_base.py CHANGED
@@ -6,8 +6,8 @@ import bottle
6
6
  from bottle import response, Bottle, request
7
7
 
8
8
  from ctools import ctoken
9
- from ctools.dict_wrapper import DictWrapper
10
9
  from ctools.api_result import R
10
+ from ctools.dict_wrapper import DictWrapper
11
11
  from ctools.sys_log import flog as log
12
12
 
13
13
  bottle.BaseRequest.MEMFILE_MAX = 1024 * 1024 * 50
@@ -22,7 +22,7 @@ def get_ws_modules():
22
22
  """
23
23
 
24
24
  """
25
- from ctools import bottle_web_base, token, bottle_webserver
25
+ from ctools import bottle_web_base, ctoken, bottle_webserver
26
26
  from ctools.api_result import R
27
27
 
28
28
  secret_key = "xxx"
@@ -35,7 +35,7 @@ def token_check():
35
35
 
36
36
  @app.post('/login')
37
37
  def login(params):
38
- return R.ok(token.gen_token({'username': 'xxx'}, secret_key, 3600))
38
+ return R.ok(ctoken.gen_token({'username': 'xxx'}, secret_key, 3600))
39
39
 
40
40
  @app.get('/demo')
41
41
  @bottle_web_base.rule('DOC:DOWNLOAD')
@@ -115,6 +115,7 @@ class CBottle:
115
115
  self.download_root = root
116
116
 
117
117
  def mount(self, context_path, app, **kwargs):
118
+ if not context_path: return
118
119
  self.bottle.mount(context_path, app, **kwargs)
119
120
 
120
121
  def init_bottle(app:Bottle=None, port=_default_port, quiet=False) -> CBottle:
ctools/cdebug.py CHANGED
@@ -14,6 +14,11 @@ class ProgramInterceptor:
14
14
  self.log_queue = Queue()
15
15
 
16
16
  def start(self):
17
+ if self.command[0] == "--log":
18
+ # 启动日志写入线程
19
+ log_thread = threading.Thread(target=self._write_log_thread, daemon=True)
20
+ log_thread.start()
21
+ self.command = self.command[1:]
17
22
  # 启动子进程
18
23
  self.process = subprocess.Popen(
19
24
  self.command,
@@ -24,10 +29,6 @@ class ProgramInterceptor:
24
29
  universal_newlines=True
25
30
  )
26
31
 
27
- # 启动日志写入线程
28
- log_thread = threading.Thread(target=self._write_log_thread, daemon=True)
29
- log_thread.start()
30
-
31
32
  # 记录初始信息
32
33
  self._enqueue_log("header", f"Command: {' '.join(self.command)}")
33
34
  self._enqueue_log("header", f"Start time: {datetime.now()}")
@@ -88,7 +89,9 @@ class ProgramInterceptor:
88
89
 
89
90
  # 等待日志写入完成
90
91
  self.log_queue.put(None) # 结束信号
91
- log_thread.join(timeout=2)
92
+ if hasattr(self, "log_thread") and isinstance(self.log_thread, threading.Thread):
93
+ if self.log_thread.is_alive():
94
+ self.log_thread.join(timeout=2)
92
95
 
93
96
  def _forward_stream(self, source, target, stream_name):
94
97
  """转发数据流并记录"""
ctools/ckafka.py CHANGED
@@ -4,12 +4,11 @@ __author__ = 'haoyang'
4
4
  __date__ = '2024/9/5 10:39'
5
5
 
6
6
  import time
7
- from threading import Thread, Lock
7
+ from threading import Thread
8
8
 
9
9
  from kafka import KafkaProducer, errors, KafkaConsumer
10
10
  from kafka.producer.future import FutureRecordMetadata
11
11
 
12
- from ctools import thread_pool
13
12
  from ctools.cjson import dumps
14
13
 
15
14
  """
ctools/credis.py CHANGED
@@ -8,6 +8,7 @@ from redis import Redis
8
8
 
9
9
  from ctools import date_utils, thread_pool, string_tools
10
10
 
11
+
11
12
  def init_pool(host: str = 'localhost', port: int = 6379, db: int = 0, password: str = None,
12
13
  username: str = None, decode_responses: bool = True, max_connections: int = 75,
13
14
  health_check_interval: int = 30, retry_count: int = 3) -> Redis:
ctools/ctoken.py CHANGED
@@ -4,6 +4,7 @@ __author__ = 'haoyang'
4
4
  __date__ = '2025/1/21 16:01'
5
5
 
6
6
  import time
7
+
7
8
  import jwt
8
9
  from bottle import request
9
10
 
ctools/czip.py CHANGED
@@ -6,6 +6,7 @@ __date__ = '2025/1/24 08:48'
6
6
  import io
7
7
  import os
8
8
  import time
9
+
9
10
  import pyzipper
10
11
 
11
12
  """
ctools/database.py CHANGED
@@ -1,7 +1,7 @@
1
1
  import contextlib
2
2
  import datetime
3
- import math
4
3
 
4
+ import math
5
5
  from sqlalchemy import create_engine, Integer, Column, event
6
6
  from sqlalchemy.ext.declarative import declarative_base
7
7
  from sqlalchemy.orm import sessionmaker, Session
ctools/douglas_rarefy.py CHANGED
@@ -4,7 +4,6 @@ __author__ = 'haoyang'
4
4
  __date__ = '2024/9/19 14:02'
5
5
 
6
6
  import math
7
-
8
7
  from jsonpath_ng import parser
9
8
 
10
9
  from ctools import cjson
ctools/http_utils.py CHANGED
@@ -16,7 +16,7 @@ def get(url, params=None, headers=None):
16
16
 
17
17
  def post(url, data=None, json=None, headers=None, files=None):
18
18
  result = ""
19
- response = requests.post(url, data=data, json=json, files=files, headers=headers, timeout=60, verify=False)
19
+ response = requests.post(url, data=data, json=json, files=files, headers=headers, timeout=600, verify=False)
20
20
  response.raise_for_status()
21
21
  if response.status_code == 200:
22
22
  result = response.content
ctools/imgDialog.py CHANGED
@@ -6,6 +6,7 @@ from tkinter import ttk
6
6
  import requests
7
7
  from PIL import Image, ImageTk
8
8
 
9
+
9
10
  def showImageTip(root, title, imagePath, tips):
10
11
  # 创建一个Tk对象
11
12
  if root:
ctools/metrics.py CHANGED
@@ -3,6 +3,7 @@ import threading
3
3
  from enum import Enum
4
4
 
5
5
  from prometheus_client import Counter, Gauge, Summary, Histogram, start_http_server
6
+
6
7
  from ctools import call, cjson, sys_log, work_path
7
8
 
8
9
  log = sys_log.flog
ctools/mqtt_utils.py CHANGED
@@ -2,11 +2,12 @@ import time
2
2
  from enum import Enum
3
3
  from typing import Dict
4
4
 
5
- from ctools.dict_wrapper import DictWrapper as DictToObj
6
5
  from paho.mqtt import client as mqtt
7
6
  from paho.mqtt.enums import CallbackAPIVersion
8
7
 
9
8
  from ctools import sys_log, cjson, string_tools, sys_info, date_utils, sm_tools, thread_pool
9
+ from ctools.dict_wrapper import DictWrapper as DictToObj
10
+
10
11
 
11
12
  class MQTTEvent(Enum):
12
13
 
ctools/pty_tools.py CHANGED
@@ -2,11 +2,12 @@ import _thread
2
2
  import queue
3
3
  import time
4
4
 
5
- # 伪终端交互
6
-
7
5
  from winpty import PtyProcess
8
6
 
9
7
 
8
+ # 伪终端交互
9
+
10
+
10
11
  class Code:
11
12
  SUCCESS = 200
12
13
  FAIL = 201
ctools/rsa.py CHANGED
@@ -1,9 +1,9 @@
1
1
  import base64
2
2
 
3
3
  from Crypto.Cipher import PKCS1_OAEP
4
+ from Crypto.Hash import SHA256
4
5
  from Crypto.PublicKey import RSA
5
6
  from Crypto.Signature import pkcs1_15
6
- from Crypto.Hash import SHA256
7
7
 
8
8
  from ctools import work_path, cjson
9
9
 
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gomyck-tools
3
- Version: 1.3.4
3
+ Version: 1.3.6
4
4
  Summary: A tools collection for python development by hao474798383
5
5
  Author-email: gomyck <hao474798383@163.com>
6
6
  License-Expression: Apache-2.0
7
- Requires-Python: >=3.10
7
+ Requires-Python: >=3.11
8
8
  Description-Content-Type: text/markdown
9
9
  License-File: LICENSE
10
10
  Requires-Dist: jsonpickle~=3.4.2
@@ -32,16 +32,12 @@ Requires-Dist: redis==5.2.1
32
32
  Provides-Extra: db
33
33
  Requires-Dist: sqlalchemy>=2.0; extra == "db"
34
34
  Requires-Dist: asyncpg>=0.28; extra == "db"
35
- Provides-Extra: dev
36
- Requires-Dist: pytest>=7.0; extra == "dev"
37
- Requires-Dist: black>=24.0; extra == "dev"
38
- Requires-Dist: mypy>=1.0; extra == "dev"
39
- Provides-Extra: full
40
- Requires-Dist: sqlalchemy>=2.0; extra == "full"
41
- Requires-Dist: asyncpg>=0.28; extra == "full"
42
- Requires-Dist: pytest>=7.0; extra == "full"
43
- Requires-Dist: black>=24.0; extra == "full"
44
- Requires-Dist: mypy>=1.0; extra == "full"
35
+ Provides-Extra: office
36
+ Requires-Dist: pandas>=2.2.3; extra == "office"
37
+ Requires-Dist: xlrd>=2.0.1; extra == "office"
38
+ Requires-Dist: python-docx>=1.1.2; extra == "office"
39
+ Provides-Extra: auto-ui
40
+ Requires-Dist: pynput==1.7.7; extra == "auto-ui"
45
41
  Dynamic: license-file
46
42
 
47
43
  # Gomyck-Tools