pytbox 0.0.1__py3-none-any.whl → 0.3.1__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 pytbox might be problematic. Click here for more details.

Files changed (68) hide show
  1. pytbox/alert/alert_handler.py +139 -0
  2. pytbox/alert/ping.py +24 -0
  3. pytbox/alicloud/sls.py +9 -14
  4. pytbox/base.py +121 -0
  5. pytbox/categraf/build_config.py +143 -0
  6. pytbox/categraf/instances.toml +39 -0
  7. pytbox/categraf/jinja2/__init__.py +6 -0
  8. pytbox/categraf/jinja2/input.cpu/cpu.toml.j2 +5 -0
  9. pytbox/categraf/jinja2/input.disk/disk.toml.j2 +11 -0
  10. pytbox/categraf/jinja2/input.diskio/diskio.toml.j2 +6 -0
  11. pytbox/categraf/jinja2/input.dns_query/dns_query.toml.j2 +12 -0
  12. pytbox/categraf/jinja2/input.http_response/http_response.toml.j2 +9 -0
  13. pytbox/categraf/jinja2/input.mem/mem.toml.j2 +5 -0
  14. pytbox/categraf/jinja2/input.net/net.toml.j2 +11 -0
  15. pytbox/categraf/jinja2/input.net_response/net_response.toml.j2 +9 -0
  16. pytbox/categraf/jinja2/input.ping/ping.toml.j2 +11 -0
  17. pytbox/categraf/jinja2/input.prometheus/prometheus.toml.j2 +12 -0
  18. pytbox/categraf/jinja2/input.snmp/cisco_interface.toml.j2 +96 -0
  19. pytbox/categraf/jinja2/input.snmp/cisco_system.toml.j2 +41 -0
  20. pytbox/categraf/jinja2/input.snmp/h3c_interface.toml.j2 +96 -0
  21. pytbox/categraf/jinja2/input.snmp/h3c_system.toml.j2 +41 -0
  22. pytbox/categraf/jinja2/input.snmp/huawei_interface.toml.j2 +96 -0
  23. pytbox/categraf/jinja2/input.snmp/huawei_system.toml.j2 +41 -0
  24. pytbox/categraf/jinja2/input.snmp/ruijie_interface.toml.j2 +96 -0
  25. pytbox/categraf/jinja2/input.snmp/ruijie_system.toml.j2 +41 -0
  26. pytbox/categraf/jinja2/input.vsphere/vsphere.toml.j2 +211 -0
  27. pytbox/cli/__init__.py +7 -0
  28. pytbox/cli/categraf/__init__.py +7 -0
  29. pytbox/cli/categraf/commands.py +55 -0
  30. pytbox/cli/commands/vm.py +22 -0
  31. pytbox/cli/common/__init__.py +6 -0
  32. pytbox/cli/common/options.py +42 -0
  33. pytbox/cli/common/utils.py +269 -0
  34. pytbox/cli/formatters/__init__.py +7 -0
  35. pytbox/cli/formatters/output.py +155 -0
  36. pytbox/cli/main.py +24 -0
  37. pytbox/cli.py +9 -0
  38. pytbox/database/mongo.py +99 -0
  39. pytbox/database/victoriametrics.py +404 -0
  40. pytbox/dida365.py +11 -17
  41. pytbox/excel.py +64 -0
  42. pytbox/feishu/endpoints.py +12 -9
  43. pytbox/{logger.py → log/logger.py} +78 -30
  44. pytbox/{victorialog.py → log/victorialog.py} +2 -2
  45. pytbox/mail/alimail.py +142 -0
  46. pytbox/mail/client.py +171 -0
  47. pytbox/mail/mail_detail.py +30 -0
  48. pytbox/mingdao.py +164 -0
  49. pytbox/network/meraki.py +537 -0
  50. pytbox/notion.py +731 -0
  51. pytbox/pyjira.py +612 -0
  52. pytbox/utils/cronjob.py +79 -0
  53. pytbox/utils/env.py +2 -2
  54. pytbox/utils/load_config.py +132 -0
  55. pytbox/utils/load_vm_devfile.py +45 -0
  56. pytbox/utils/response.py +1 -1
  57. pytbox/utils/richutils.py +31 -0
  58. pytbox/utils/timeutils.py +479 -14
  59. pytbox/vmware.py +120 -0
  60. pytbox/win/ad.py +30 -0
  61. {pytbox-0.0.1.dist-info → pytbox-0.3.1.dist-info}/METADATA +13 -3
  62. pytbox-0.3.1.dist-info/RECORD +72 -0
  63. pytbox-0.3.1.dist-info/entry_points.txt +2 -0
  64. pytbox/common/base.py +0 -0
  65. pytbox/victoriametrics.py +0 -37
  66. pytbox-0.0.1.dist-info/RECORD +0 -21
  67. {pytbox-0.0.1.dist-info → pytbox-0.3.1.dist-info}/WHEEL +0 -0
  68. {pytbox-0.0.1.dist-info → pytbox-0.3.1.dist-info}/top_level.txt +0 -0
pytbox/utils/timeutils.py CHANGED
@@ -1,40 +1,505 @@
1
1
  #!/usr/bin/env python3
2
2
 
3
-
4
- import time
3
+ import re
5
4
  import pytz
5
+ import time
6
6
  import datetime
7
+ from zoneinfo import ZoneInfo
8
+ from typing import Literal
7
9
 
8
10
 
9
11
  class TimeUtils:
10
12
 
11
13
  @staticmethod
12
- def get_timestamp(now: bool=True) -> int:
14
+ def get_time_object(now: bool=True):
13
15
  '''
14
- 获取时间戳
16
+ 获取当前时间, 加入了时区信息, 简单是存储在 Mongo 中时格式为 ISODate
17
+
18
+ Returns:
19
+ current_time(class): 时间, 格式: 2024-04-23 16:48:11.591589+08:00
20
+ '''
21
+ if now:
22
+ return datetime.datetime.now(pytz.timezone('Asia/Shanghai'))
23
+
24
+ @staticmethod
25
+ def get_utc_time():
26
+ return datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
27
+
28
+ @staticmethod
29
+ def get_now_time_mongo():
30
+ return datetime.datetime.now(pytz.timezone('Asia/Shanghai'))
31
+
32
+ @staticmethod
33
+ def convert_timeobj_to_str(timeobj: str=None, timezone_offset: int=8, time_format: Literal['%Y-%m-%d %H:%M:%S', '%Y-%m-%dT%H:%M:%SZ']='%Y-%m-%d %H:%M:%S'):
34
+ time_obj_with_offset = timeobj + datetime.timedelta(hours=timezone_offset)
35
+ if time_format == '%Y-%m-%d %H:%M:%S':
36
+ return time_obj_with_offset.strftime("%Y-%m-%d %H:%M:%S")
37
+ elif time_format == '%Y-%m-%dT%H:%M:%SZ':
38
+ return time_obj_with_offset.strftime("%Y-%m-%dT%H:%M:%SZ")
39
+
40
+ @staticmethod
41
+ def get_time_diff_hours(time1, time2):
42
+ """
43
+ 计算两个datetime对象之间的小时差
44
+
45
+ Args:
46
+ time1: 第一个datetime对象
47
+ time2: 第二个datetime对象
48
+
49
+ Returns:
50
+ float: 两个时间之间的小时差
51
+ """
52
+ if not time1 or not time2:
53
+ return 0
54
+
55
+ # 确保两个时间都有时区信息
56
+ if time1.tzinfo is None:
57
+ time1 = time1.replace(tzinfo=pytz.timezone('Asia/Shanghai'))
58
+ if time2.tzinfo is None:
59
+ time2 = time2.replace(tzinfo=pytz.timezone('Asia/Shanghai'))
15
60
 
61
+ # 计算时间差(秒)
62
+ time_diff_seconds = abs((time2 - time1).total_seconds())
63
+
64
+ # 转换为小时
65
+ time_diff_hours = time_diff_seconds / 3600
66
+
67
+ return time_diff_hours
68
+
69
+ @staticmethod
70
+ def convert_syslog_huawei_str_to_8601(timestr):
71
+ """
72
+ 将华为 syslog 格式的时间字符串(如 '2025-08-02T04:34:24+08:00')转换为 ISO8601 格式的 UTC 时间字符串。
73
+
16
74
  Args:
17
- now (bool, optional): _description_. Defaults to True.
75
+ timestr (str): 原始时间字符串,格式如 '2025-08-02T04:34:24+08:00'
76
+
77
+ Returns:
78
+ str: 转换后的 ISO8601 格式 UTC 时间字符串,如 '2025-08-01T20:34:24.000000Z'
79
+ """
80
+ if timestr is None:
81
+ return None
82
+ try:
83
+ # 解析带时区的时间字符串
84
+ dt: datetime.datetime = datetime.datetime.strptime(timestr, "%Y-%m-%dT%H:%M:%S%z")
85
+ # 转换为 UTC
86
+ dt_utc: datetime.datetime = dt.astimezone(datetime.timezone.utc)
87
+ # 格式化为 ISO8601 字符串(带微秒,Z 结尾)
88
+ iso8601_utc: str = dt_utc.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
89
+ return iso8601_utc
90
+ except ValueError as e:
91
+ # 日志记录异常(此处仅简单打印,实际项目建议用 logging)
92
+ print(f"时间转换失败: {e}, 输入: {timestr}")
93
+ return None
94
+
95
+ @staticmethod
96
+ def convert_str_to_timestamp(timestr):
97
+ """
98
+ 将类似 '2025-04-16T00:08:28.000+0000' 格式的时间字符串转换为时间戳(秒级)。
99
+
100
+ Args:
101
+ timestr (str): 时间字符串,格式如 '2025-04-16T00:08:28.000+0000'
102
+
103
+ Returns:
104
+ int: 时间戳(秒级)
105
+ """
106
+ if timestr is None:
107
+ return None
108
+ # 兼容带毫秒和时区的ISO8601格式
109
+ # 先将+0000或+08:00等时区格式标准化为+00:00
110
+ timestr_fixed = re.sub(r'([+-]\d{2})(\d{2})$', r'\1:\2', timestr)
111
+
112
+ # 处理毫秒部分(.000),如果没有毫秒也能兼容
113
+ try:
114
+ dt = datetime.datetime.fromisoformat(timestr_fixed)
115
+ except ValueError:
116
+ if len(timestr_fixed) == 8:
117
+ dt = datetime.datetime.strptime(timestr_fixed, "%Y%m%d")
118
+ else:
119
+ # 如果没有毫秒部分
120
+ dt = datetime.datetime.strptime(timestr_fixed, "%Y-%m-%dT%H:%M:%S%z")
121
+ # 返回秒级时间戳
122
+ return int(dt.timestamp()) * 1000
123
+
124
+ @staticmethod
125
+ def convert_timestamp_to_str(timestamp: int, time_format: Literal['%Y-%m-%d %H:%M:%S', '%Y-%m-%dT%H:%M:%SZ', '%Y-%m-%dT%H:%M:%S.000Z']='%Y-%m-%d %H:%M:%S', timezone_offset: int=8):
126
+ '''
127
+ _summary_
128
+
129
+ Args:
130
+ timestamp (_type_): _description_
18
131
 
19
132
  Returns:
20
133
  _type_: _description_
21
134
  '''
135
+ timestamp = int(timestamp)
136
+ if timestamp > 10000000000:
137
+ timestamp = timestamp / 1000
138
+ # 使用datetime模块的fromtimestamp方法将时间戳转换为datetime对象
139
+ dt_object = datetime.datetime.fromtimestamp(timestamp, tz=ZoneInfo(f'Etc/GMT-{timezone_offset}'))
140
+
141
+ # 使用strftime方法将datetime对象格式化为字符串
142
+ return dt_object.strftime(time_format)
143
+
144
+ @staticmethod
145
+ def timestamp_to_timestr_dida(timestamp):
146
+ # 将时间戳转换为 UTC 时间
147
+ utc_time = datetime.datetime.fromtimestamp(timestamp, tz=datetime.timezone.utc)
148
+
149
+ # 将 UTC 时间转换为指定时区(+08:00)
150
+ target_timezone = datetime.timezone(datetime.timedelta(hours=8))
151
+ local_time = utc_time.astimezone(target_timezone)
152
+
153
+ # 格式化为指定字符串
154
+ formatted_time = local_time.strftime('%Y-%m-%dT%H:%M:%S%z')
155
+
156
+ # 添加冒号到时区部分
157
+ formatted_time = formatted_time[:-2] + ':' + formatted_time[-2:]
158
+ return formatted_time
159
+
160
+ @staticmethod
161
+ def timestamp_to_datetime_obj(timestamp: int, timezone_offset: int=8):
162
+ return datetime.datetime.fromtimestamp(timestamp, tz=ZoneInfo(f'Etc/GMT-{timezone_offset}'))
163
+
164
+ @staticmethod
165
+ def datetime_obj_to_str(datetime_obj, add_timezone=False):
166
+ if add_timezone:
167
+ datetime_obj = datetime_obj.astimezone(pytz.timezone('Asia/Shanghai'))
168
+ return datetime_obj.strftime("%Y-%m-%d %H:%M:%S")
169
+
170
+ @staticmethod
171
+ def get_current_time(app: Literal['notion', 'dida365']):
172
+ if app == 'notion':
173
+ return datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
174
+ elif app == 'dida365':
175
+ return datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S%z")
176
+
177
+ @staticmethod
178
+ def get_current_date_str(output_format: Literal['%Y%m%d', '%Y-%m-%d']='%Y%m%d') -> str:
179
+ # 获取当前日期和时间
180
+ now = datetime.datetime.now()
181
+
182
+ # 格式化为字符串
183
+ current_date_str = now.strftime(output_format)
184
+ return current_date_str
185
+
186
+ @staticmethod
187
+ def get_date_n_days_from_now(n: int = 0, output_format: Literal['%Y%m%d', '%Y-%m-%d'] = '%Y%m%d') -> str:
188
+ """
189
+ 获取距离当前日期 N 天后的日期字符串。
190
+
191
+ Args:
192
+ n (int): 天数,可以为负数(表示 N 天前),默认为 0(即今天)。
193
+ output_format (Literal['%Y%m%d', '%Y-%m-%d']): 日期输出格式,默认为 '%Y%m%d'。
194
+
195
+ Returns:
196
+ str: 格式化后的日期字符串。
197
+
198
+ 示例:
199
+ >>> MyTime.get_date_n_days_from_now(1)
200
+ '20240620'
201
+ >>> MyTime.get_date_n_days_from_now(-1, '%Y-%m-%d')
202
+ '2024-06-18'
203
+ """
204
+ target_date = datetime.datetime.now() + datetime.timedelta(days=n)
205
+ return target_date.strftime(output_format)
206
+
207
+
208
+ @staticmethod
209
+ def get_yesterday_date_str() -> str:
210
+ # 获取昨天日期
211
+ yesterday = datetime.datetime.now() - datetime.timedelta(days=1)
212
+ yesterday_date_str = yesterday.strftime("%Y%m%d")
213
+ return yesterday_date_str
214
+
215
+ @staticmethod
216
+ def get_last_month_date_str() -> str:
217
+ # 获取上个月日期
218
+ last_month = datetime.datetime.now() - datetime.timedelta(days=30)
219
+ last_month_date_str = last_month.strftime("%Y-%m")
220
+ return last_month_date_str
221
+
222
+ @staticmethod
223
+ def get_current_time_str() -> str:
224
+ # 获取当前日期和时间
225
+ now = datetime.datetime.now()
226
+
227
+ # 格式化为字符串
228
+ current_date_str = now.strftime("%Y%m%d_%H%M")
229
+ return current_date_str
230
+
231
+ @staticmethod
232
+ def get_timestamp(now: bool=True, last_minutes: int=0, unit: Literal['ms', 's']='ms') -> int:
233
+ '''
234
+ 获取当前时间戳, 减去 last_minutes 分钟
235
+ '''
22
236
  if now:
23
- return int(time.time())
237
+ if unit == 'ms':
238
+ return int(time.time()) * 1000
239
+ elif unit == 's':
240
+ return int(time.time())
241
+
242
+ if last_minutes == 0:
243
+ if unit == 'ms':
244
+ return int(time.time()) * 1000
245
+ elif unit == 's':
246
+ return int(time.time())
24
247
  else:
25
- return int(time.time() * 1000)
248
+ if unit == 'ms':
249
+ return int(time.time()) * 1000 - last_minutes * 60 * 1000
250
+ elif unit == 's':
251
+ return int(time.time()) - last_minutes * 60
252
+
253
+ @staticmethod
254
+ def get_timestamp_tomorrow() -> int:
255
+ return int(time.time()) * 1000 + 24 * 60 * 60 * 1000
26
256
 
27
257
  @staticmethod
28
- def get_time_object(now: bool=True):
258
+ def get_timestamp_last_day(last_days: int=0, unit: Literal['ms', 's']='ms') -> int:
259
+ if last_days == 0:
260
+ if unit == 'ms':
261
+ return int(time.time()) * 1000
262
+ elif unit == 's':
263
+ return int(time.time())
264
+ else:
265
+ return int(time.time()) * 1000 - last_days * 24 * 60 * 60 * 1000
266
+
267
+ @staticmethod
268
+ def get_today_timestamp() -> int:
269
+ return int(time.time()) * 1000
270
+
271
+
272
+ @staticmethod
273
+ def convert_timeobj_to_str(timeobj: str=None, timezone_offset: int=8, time_format: Literal['%Y-%m-%d %H:%M:%S', '%Y-%m-%dT%H:%M:%SZ']='%Y-%m-%d %H:%M:%S'):
274
+ time_obj_with_offset = timeobj + datetime.timedelta(hours=timezone_offset)
275
+ if time_format == '%Y-%m-%d %H:%M:%S':
276
+ return time_obj_with_offset.strftime("%Y-%m-%d %H:%M:%S")
277
+ elif time_format == '%Y-%m-%dT%H:%M:%SZ':
278
+ return time_obj_with_offset.strftime("%Y-%m-%dT%H:%M:%SZ")
279
+
280
+ @staticmethod
281
+ def convert_timestamp_to_timeobj(timestamp: int) -> datetime.datetime:
282
+ """
283
+ 将时间戳转换为时间对象
284
+
285
+ Args:
286
+ timestamp (int): 时间戳,单位为秒或毫秒
287
+
288
+ Returns:
289
+ datetime.datetime: 转换后的时间对象,时区为 Asia/Shanghai
290
+ """
291
+ # 如果时间戳是毫秒,转换为秒
292
+ if len(str(timestamp)) > 10:
293
+ timestamp = timestamp / 1000
294
+
295
+ return datetime.datetime.fromtimestamp(timestamp, tz=ZoneInfo("Asia/Shanghai"))
296
+
297
+ @staticmethod
298
+ def convert_timeobj_to_timestamp(timeobj: datetime.datetime) -> int:
299
+ """
300
+ 将时间对象转换为时间戳
301
+
302
+ Args:
303
+ timeobj (datetime.datetime): 时间对象
304
+
305
+ Returns:
306
+ int: 时间戳,单位为秒或毫秒
307
+ """
308
+ return int(timeobj.timestamp())
309
+
310
+ @staticmethod
311
+ def convert_timeobj_add_timezone(timeobj: datetime.datetime, timezone_offset: int=8) -> datetime.datetime:
312
+ return timeobj + datetime.timedelta(hours=timezone_offset)
313
+
314
+ @staticmethod
315
+ def convert_mute_duration(mute_duration: str) -> datetime.datetime:
316
+ """将屏蔽时长字符串转换为带 Asia/Shanghai 时区的 ``datetime`` 对象。
317
+
318
+ 支持两类输入:
319
+ - 绝对时间: 例如 ``"2025-01-02 13:45"``,按本地上海时区解释。
320
+ - 相对时间: 形如 ``"10m"``、``"2h"``、``"1d"`` 分别表示分钟、小时、天。
321
+
322
+ Args:
323
+ mute_duration: 屏蔽时长字符串。
324
+
325
+ Returns:
326
+ datetime.datetime: 带 ``Asia/Shanghai`` 时区信息的时间点。
327
+
328
+ Raises:
329
+ ValueError: 当输入字符串无法被解析时抛出。
330
+ """
331
+ shanghai_tz = ZoneInfo("Asia/Shanghai")
332
+ now: datetime.datetime = datetime.datetime.now(tz=shanghai_tz)
333
+ mute_duration = mute_duration.strip()
334
+
335
+ # 绝对时间格式(按上海时区解释)
336
+ try:
337
+ abs_dt_naive = datetime.datetime.strptime(mute_duration, "%Y-%m-%d %H:%M")
338
+ return abs_dt_naive.replace(tzinfo=shanghai_tz)
339
+ except ValueError:
340
+ pass
341
+
342
+ # 相对时间格式
343
+ pattern = r"^(\d+)([dhm])$"
344
+ match = re.match(pattern, mute_duration)
345
+ if match:
346
+ value_str, unit = match.groups()
347
+ value = int(value_str)
348
+ if unit == "d":
349
+ return now + datetime.timedelta(days=value)
350
+ if unit == "h":
351
+ return now + datetime.timedelta(hours=value)
352
+ if unit == "m":
353
+ return now + datetime.timedelta(minutes=value)
354
+
355
+ raise ValueError(f"无法解析 mute_duration: {mute_duration}")
356
+
357
+ @staticmethod
358
+ def convert_mute_duration_to_str(mute_duration: str) -> str:
29
359
  '''
30
- 获取当前时间, 加入了时区信息, 简单是存储在 Mongo 中时格式为 ISODate
360
+ 将屏蔽时长字符串转换为距离当前时间的标准描述,形如 '2d3h'。
361
+
362
+ 支持两类输入:
363
+ - 绝对时间: 'YYYY-MM-DD HH:MM'(按 Asia/Shanghai 解释)
364
+ - 相对时间: '10m'、'2h'、'1d'
365
+
366
+ 如果目标时间已过去,则返回 '0h'。
367
+
368
+ Args:
369
+ mute_duration (str): 屏蔽时长字符串。
31
370
 
32
371
  Returns:
33
- current_time(class): 时间, 格式: 2024-04-23 16:48:11.591589+08:00
372
+ str: 与当前时间的距离描述,例如 '1d2h'、'3h'。
34
373
  '''
35
- if now:
36
- return datetime.datetime.now(pytz.timezone('Asia/Shanghai'))
374
+ shanghai_tz = ZoneInfo("Asia/Shanghai")
375
+ now: datetime.datetime = datetime.datetime.now(tz=shanghai_tz)
376
+
377
+ try:
378
+ target_time: datetime.datetime = TimeUtils.convert_mute_duration(mute_duration)
379
+ except ValueError:
380
+ # 兜底:直接尝试绝对时间格式
381
+ try:
382
+ abs_dt = datetime.datetime.strptime(mute_duration.strip(), "%Y-%m-%d %H:%M")
383
+ target_time = abs_dt.replace(tzinfo=shanghai_tz)
384
+ except ValueError:
385
+ return mute_duration
386
+
387
+ # 计算与现在的差值(仅面向未来的剩余时长)
388
+ delta_seconds: float = (target_time - now).total_seconds()
389
+ if delta_seconds <= 0:
390
+ return "0h"
391
+
392
+ days: int = int(delta_seconds // 86400)
393
+ hours: int = int((delta_seconds % 86400) // 3600)
394
+
395
+ parts: list[str] = []
396
+ if days > 0:
397
+ parts.append(f"{days}d")
398
+ if hours > 0:
399
+ parts.append(f"{hours}h")
400
+ if not parts:
401
+ # 小于 1 小时
402
+ parts.append("0h")
403
+
404
+ return "".join(parts)
405
+
406
+ @staticmethod
407
+ def is_work_time(start_hour: int=9, end_hour: int=18) -> bool:
408
+ '''
409
+ 判断是否为工作时间
410
+
411
+ Args:
412
+ start_hour (int, optional): 开始工作时间. Defaults to 9.
413
+ end_hour (int, optional): 结束工作时间. Defaults to 18.
414
+
415
+ Returns:
416
+ bool: 如果是工作时间, 返回 True, 否则返回 False
417
+ '''
418
+ current_time = datetime.datetime.now().time()
419
+ start = datetime.time(start_hour, 0, 0)
420
+ end = datetime.time(end_hour, 0, 0)
421
+
422
+ if start <= current_time <= end:
423
+ return True
424
+ else:
425
+ return False
37
426
 
38
427
  @staticmethod
39
- def get_utc_time():
40
- return datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
428
+ def is_work_day() -> bool:
429
+ '''
430
+ 判断是否为工作日
431
+
432
+ Returns:
433
+ bool: 如果是工作日, 返回 True, 否则返回 False
434
+ '''
435
+ from chinese_calendar import is_workday
436
+ date_now = datetime.datetime.date(datetime.datetime.now())
437
+ # print(date_now)
438
+ if is_workday(date_now):
439
+ return True
440
+ else:
441
+ return False
442
+
443
+ @staticmethod
444
+ def get_week_number(offset: int=5) -> int:
445
+ '''
446
+ 获取今天是哪一年的第几周
447
+
448
+ Returns:
449
+ int: 返回一个元组,包含(年份, 周数)
450
+ '''
451
+ now = datetime.datetime.now()
452
+ # isocalendar()方法返回一个元组,包含年份、周数和周几
453
+ _year_unused, week, _ = now.isocalendar()
454
+ return week - offset
455
+
456
+ @staticmethod
457
+ def get_week_day(timestamp: int=None, offset: int=0) -> int:
458
+ '''
459
+ 获取今天是周几
460
+
461
+ Returns:
462
+ int: 周几的数字表示,1表示周一,7表示周日
463
+ '''
464
+ if timestamp is None:
465
+ now = datetime.datetime.now()
466
+ else:
467
+ if timestamp > 10000000000:
468
+ timestamp = timestamp / 1000
469
+ now = datetime.datetime.fromtimestamp(timestamp)
470
+ # weekday()方法返回0-6的数字,0表示周一,6表示周日
471
+ return now.weekday() + 1 + offset
472
+
473
+ @staticmethod
474
+ def get_last_month_start_and_end_time() -> tuple[datetime.datetime, datetime.datetime]:
475
+ '''
476
+ 获取上个月的开始和结束时间
477
+
478
+ Returns:
479
+ tuple[datetime.datetime, datetime.datetime]: 返回一个元组,包含上个月的开始和结束时间
480
+ '''
481
+ today = datetime.datetime.now()
482
+ # 获取当前月份的第一天
483
+ first_day_of_current_month = today.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
484
+ # 上个月1号的0:00分
485
+ start_time = first_day_of_current_month - datetime.timedelta(days=1)
486
+ start_time = start_time.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
487
+
488
+ # 获取上个月的最后一天
489
+ end_time = first_day_of_current_month - datetime.timedelta(days=1)
490
+ end_time = end_time.replace(hour=23, minute=59, second=59, microsecond=0)
491
+ return start_time.strftime("%Y-%m-%dT%H:%M:%SZ"), end_time.strftime("%Y-%m-%dT%H:%M:%SZ")
492
+
493
+ @staticmethod
494
+ def convert_rfc3339_to_unix_ms(ts_str: str) -> int:
495
+ """
496
+ 将 RFC3339 格式的时间字符串转换为毫秒级时间戳
497
+
498
+ Args:
499
+ ts_str (str): RFC3339 格式的时间字符串
500
+
501
+ Returns:
502
+ int: 毫秒级时间戳
503
+ """
504
+ dt = datetime.datetime.fromisoformat(ts_str.replace("Z", "+00:00"))
505
+ return int(dt.timestamp() * 1000)
pytbox/vmware.py ADDED
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env python3
2
+
3
+
4
+ from typing import Any, Dict, Optional, Union, Literal
5
+ import urllib3
6
+ urllib3.disable_warnings()
7
+ import requests
8
+ from requests.auth import HTTPBasicAuth
9
+
10
+ from .utils.response import ReturnResponse
11
+
12
+
13
+ class VMwareClient:
14
+ """VMware vSphere Automation API 客户端。
15
+
16
+ 支持多种认证方式:
17
+ 1. Basic Auth - HTTP 基础认证
18
+ 2. API Key Auth - 使用会话 ID 认证
19
+ 3. Federated Identity Auth - 联合身份认证(Bearer Token)
20
+ """
21
+
22
+ def __init__(
23
+ self,
24
+ host: str=None,
25
+ username: str=None,
26
+ password: str=None,
27
+ version: Literal['6.7', '7.0']='6.7',
28
+ proxies: dict=None,
29
+ verify_ssl: bool = False,
30
+ timeout: int = 30,
31
+ ) -> None:
32
+ """初始化 VMware 客户端。
33
+
34
+ Args:
35
+ host: vCenter Server 主机地址(例如:https://vcenter.example.com)
36
+ verify_ssl: 是否验证 SSL 证书
37
+ timeout: 请求超时时间(秒)
38
+ proxy_host: 代理服务器主机地址
39
+ proxy_port: 代理服务器端口
40
+ proxy_username: 代理认证用户名(可选)
41
+ proxy_password: 代理认证密码(可选)
42
+ """
43
+ self.host = host
44
+ self.username = username
45
+ self.password = password
46
+ self.version = version
47
+ self.proxies = proxies
48
+ self.verify_ssl = verify_ssl
49
+ self.timeout = timeout
50
+ self.session_id: Optional[str] = None
51
+
52
+ self.headers = {
53
+ "vmware-api-session-id": self.get_session()
54
+ }
55
+
56
+ def get_session(self) -> str:
57
+ """获取 VMware vSphere API 会话 ID。
58
+
59
+ Returns:
60
+ 会话 ID 字符串
61
+ """
62
+ if self.version == '6.7':
63
+ url = f"{self.host}/rest/com/vmware/cis/session"
64
+ else:
65
+ url = f"{self.host}/api/session"
66
+
67
+ response = requests.post(
68
+ url,
69
+ auth=HTTPBasicAuth(self.username, self.password),
70
+ timeout=self.timeout,
71
+ verify=self.verify_ssl,
72
+ proxies=self.proxies
73
+ )
74
+
75
+ if response.status_code == 200 or response.status_code == 201:
76
+ # vSphere API 通常直接返回 session ID 字符串
77
+ session_id = response.json()
78
+ try:
79
+ return session_id['value']
80
+ except Exception:
81
+ return session_id
82
+ else:
83
+ return f"认证失败: {response.status_code} - {response.text}"
84
+
85
+ def get_vm_list(self) -> ReturnResponse:
86
+ '''
87
+ _summary_
88
+
89
+ Returns:
90
+ ReturnResponse: _description_
91
+ '''
92
+ if self.version == '6.7':
93
+ url = f"{self.host}/rest/vcenter/vm"
94
+ else:
95
+ url = f"{self.host}/api/vcenter/vm"
96
+ response = requests.get(url, headers=self.headers, timeout=self.timeout, verify=False, proxies=self.proxies)
97
+ if response.status_code == 200:
98
+ if self.version == '6.7':
99
+ data = response.json().get('value')
100
+ else:
101
+ data = response.json()
102
+ return ReturnResponse(code=0, msg=f'成功获取到 {len(data)} 台虚拟机', data=data)
103
+ else:
104
+ return ReturnResponse(code=1, msg='error', data=response.json())
105
+
106
+ def get_vm(self, vm_id):
107
+ if self.version == '6.7':
108
+ url = f"{self.host}/rest/vcenter/vm/{vm_id}"
109
+ else:
110
+ url = f"{self.host}/api/vcenter/vm/{vm_id}"
111
+ response = requests.get(url, headers=self.headers, timeout=self.timeout, verify=False, proxies=self.proxies)
112
+ if response.status_code == 200:
113
+ return ReturnResponse(code=0, msg='成功获取到虚拟机', data=response.json())
114
+ else:
115
+ return ReturnResponse(code=1, msg=f"{response.status_code}, {response.text}", data=response.json())
116
+
117
+ # 使用示例
118
+ if __name__ == "__main__":
119
+ pass
120
+
pytbox/win/ad.py ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env python3
2
+
3
+
4
+ from ldap3 import Server, Connection, ALL, SUBTREE, MODIFY_REPLACE, SUBTREE, MODIFY_ADD, MODIFY_DELETE
5
+
6
+
7
+ class ADClient:
8
+ '''
9
+ _summary_
10
+ '''
11
+ def __init__(self, server, base_dn, username, password):
12
+ self.server = Server(server, get_info=ALL)
13
+ self.conn = Connection(self.server, user=username, password=password, auto_bind=True)
14
+ self.base_dn = base_dn
15
+
16
+ def list_user(self):
17
+ '''
18
+ 查询所有用户
19
+
20
+ Yields:
21
+ dict: 返回的是生成器, 字典类型
22
+ '''
23
+ # 搜索过滤条件
24
+ secarch_filter = '(objectCategory=person)' # 过滤所有用户
25
+ # SEARCH_ATTRIBUTES = ['cn', 'sAMAccountName', 'mail', 'userPrincipalName'] # 需要的用户属性
26
+ search_attributes = ["*"] # 需要的用户属性
27
+ # 搜索用户
28
+ if self.conn.search(search_base=self.base_dn, search_filter=secarch_filter, search_scope=SUBTREE, attributes=search_attributes):
29
+ for entry in self.conn.entries:
30
+ yield {k: v[0] if isinstance(v, list) else v for k, v in entry.entry_attributes_as_dict.items()}