funcguard 0.2.55__tar.gz → 0.2.56__tar.gz

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.
Files changed (36) hide show
  1. {funcguard-0.2.55 → funcguard-0.2.56}/PKG-INFO +10 -1
  2. {funcguard-0.2.55 → funcguard-0.2.56}/README.md +9 -0
  3. {funcguard-0.2.55 → funcguard-0.2.56}/funcguard/__init__.py +6 -0
  4. funcguard-0.2.56/funcguard/data_models/request_models.py +11 -0
  5. {funcguard-0.2.55 → funcguard-0.2.56}/funcguard/ip_utils.py +2 -1
  6. {funcguard-0.2.55 → funcguard-0.2.56}/funcguard/log_utils.py +21 -21
  7. {funcguard-0.2.55 → funcguard-0.2.56}/funcguard/pd_utils/convert_utils.py +15 -21
  8. {funcguard-0.2.55 → funcguard-0.2.56}/funcguard/pd_utils/date_utils.py +6 -6
  9. {funcguard-0.2.55 → funcguard-0.2.56}/funcguard/pd_utils/fill_utils.py +9 -5
  10. {funcguard-0.2.55 → funcguard-0.2.56}/funcguard/pd_utils/filter.py +1 -2
  11. {funcguard-0.2.55 → funcguard-0.2.56}/funcguard/pd_utils/statistics/agg_utils.py +6 -6
  12. {funcguard-0.2.55 → funcguard-0.2.56}/funcguard/pd_utils/statistics/count_utils.py +10 -9
  13. {funcguard-0.2.55 → funcguard-0.2.56}/funcguard/pd_utils/statistics/df_statistics.py +27 -22
  14. {funcguard-0.2.55 → funcguard-0.2.56}/funcguard/pd_utils/statistics/mask_utils.py +13 -9
  15. {funcguard-0.2.55 → funcguard-0.2.56}/funcguard/tools.py +16 -9
  16. {funcguard-0.2.55 → funcguard-0.2.56}/funcguard.egg-info/PKG-INFO +10 -1
  17. {funcguard-0.2.55 → funcguard-0.2.56}/setup.py +1 -1
  18. funcguard-0.2.55/funcguard/data_models/request_models.py +0 -12
  19. {funcguard-0.2.55 → funcguard-0.2.56}/LICENSE +0 -0
  20. {funcguard-0.2.55 → funcguard-0.2.56}/funcguard/calculate.py +0 -0
  21. {funcguard-0.2.55 → funcguard-0.2.56}/funcguard/core.py +0 -0
  22. {funcguard-0.2.55 → funcguard-0.2.56}/funcguard/data_models/__init__.py +0 -0
  23. {funcguard-0.2.55 → funcguard-0.2.56}/funcguard/pd_utils/__init__.py +0 -0
  24. {funcguard-0.2.55 → funcguard-0.2.56}/funcguard/pd_utils/json_utils/__init__.py +0 -0
  25. {funcguard-0.2.55 → funcguard-0.2.56}/funcguard/pd_utils/json_utils/json_parser.py +0 -0
  26. {funcguard-0.2.55 → funcguard-0.2.56}/funcguard/pd_utils/statistics/__init__.py +0 -0
  27. {funcguard-0.2.55 → funcguard-0.2.56}/funcguard/printer.py +0 -0
  28. {funcguard-0.2.55 → funcguard-0.2.56}/funcguard/time_utils.py +0 -0
  29. {funcguard-0.2.55 → funcguard-0.2.56}/funcguard.egg-info/SOURCES.txt +0 -0
  30. {funcguard-0.2.55 → funcguard-0.2.56}/funcguard.egg-info/dependency_links.txt +0 -0
  31. {funcguard-0.2.55 → funcguard-0.2.56}/funcguard.egg-info/not-zip-safe +0 -0
  32. {funcguard-0.2.55 → funcguard-0.2.56}/funcguard.egg-info/requires.txt +0 -0
  33. {funcguard-0.2.55 → funcguard-0.2.56}/funcguard.egg-info/top_level.txt +0 -0
  34. {funcguard-0.2.55 → funcguard-0.2.56}/setup.cfg +0 -0
  35. {funcguard-0.2.55 → funcguard-0.2.56}/tests/__init__.py +0 -0
  36. {funcguard-0.2.55 → funcguard-0.2.56}/tests/test_pd_filter_empty.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: funcguard
3
- Version: 0.2.55
3
+ Version: 0.2.56
4
4
  Summary: FuncGuard是一个Python库,提供函数执行超时控制、重试机制、HTTP请求封装和格式化打印工具。
5
5
  Home-page: https://github.com/tinycen/funcguard
6
6
  Author: tinycen
@@ -188,6 +188,15 @@ response = send_request(
188
188
  )
189
189
  print(response)
190
190
 
191
+ # 带 URL 查询参数的 GET 请求
192
+ response = send_request(
193
+ method="GET",
194
+ url="https://api.example.com/users",
195
+ params={"page": 1, "size": 20, "search": "张三"}
196
+ # 等价于: https://api.example.com/users?page=1&size=20&search=%E5%BC%A0%E4%B8%89
197
+ )
198
+ print(response)
199
+
191
200
  # POST 请求(自动将 dict/list 转为 JSON)
192
201
  response = send_request(
193
202
  method="POST",
@@ -156,6 +156,15 @@ response = send_request(
156
156
  )
157
157
  print(response)
158
158
 
159
+ # 带 URL 查询参数的 GET 请求
160
+ response = send_request(
161
+ method="GET",
162
+ url="https://api.example.com/users",
163
+ params={"page": 1, "size": 20, "search": "张三"}
164
+ # 等价于: https://api.example.com/users?page=1&size=20&search=%E5%BC%A0%E4%B8%89
165
+ )
166
+ print(response)
167
+
159
168
  # POST 请求(自动将 dict/list 转为 JSON)
160
169
  response = send_request(
161
170
  method="POST",
@@ -1,3 +1,9 @@
1
+ try:
2
+ from importlib.metadata import version as _get_version
3
+ __version__ = _get_version("funcguard")
4
+ except Exception:
5
+ __version__ = "unknown"
6
+
1
7
  from .core import timeout_handler, retry_function, ask_select
2
8
  from .tools import send_request, curl_cffi_request, check_url_valid, encode_basic_auth, md5_hash
3
9
  from .time_utils import (
@@ -0,0 +1,11 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass
5
+ class RequestLog:
6
+ save_method: bool | None = True
7
+ save_url: bool | None = True
8
+ save_headers: bool | None = True
9
+ save_body: bool | None = True
10
+ save_response: bool | None = True
11
+ save_path: str | None = ""
@@ -53,7 +53,8 @@ def get_public_ip():
53
53
  return ip
54
54
  except Exception:
55
55
  continue
56
-
56
+
57
+ print("警告: 所有公网IP查询服务均失败,请检查网络连接")
57
58
  return None
58
59
  except Exception as e:
59
60
  print(f"获取公网IP地址失败: {e}")
@@ -2,7 +2,7 @@
2
2
 
3
3
  import logging
4
4
  import sys
5
- from typing import Optional, TextIO, Union, cast
5
+ from typing import TextIO, cast
6
6
 
7
7
 
8
8
  class ColoredFormatter(logging.Formatter):
@@ -92,7 +92,7 @@ if not hasattr(logging.Logger, "progress"):
92
92
  setattr(logging.Logger, "progress", _logger_progress)
93
93
 
94
94
 
95
- def _normalize_level(level: Union[int, str]) -> int:
95
+ def _normalize_level(level: int | str) -> int:
96
96
  """
97
97
  规范化日志等级。
98
98
 
@@ -113,32 +113,32 @@ def _normalize_level(level: Union[int, str]) -> int:
113
113
 
114
114
 
115
115
  def setup_logger(
116
- name: Optional[str] = None,
117
- level: Union[int, str] = logging.DEBUG,
118
- stream: Optional[TextIO] = None,
116
+ name: str | None = None,
117
+ level: int | str = logging.DEBUG,
118
+ stream: TextIO | None = None,
119
119
  message_only: bool = False,
120
120
  ) -> SuccessLogger:
121
121
  """
122
122
  创建并配置彩色日志输出。
123
123
 
124
124
  Args:
125
- name: logger 的名称,默认 全局共享1个 logger 实例。
125
+ name: logger 的名称,默认全局共享1个 logger 实例。
126
126
  示例:
127
- # 在 network.py
128
- logger_a = setup_logger("network")
129
- logger_a.debug("网络调试信息")
130
-
131
- # 在 db.py
132
- logger_b = setup_logger("db")
133
- logger_b.debug("数据库调试信息")
134
- 作用:
135
- 通过不同名称创建的 logger 互不干扰,适合在大型项目中使用。
136
- 每个 logger 设置不同的日志级别、格式、输出流等。
137
- level: 日志等级。支持 int 或字符串,默认 "DEBUG"。根据等级过滤后交给 handler 输出。
138
- 常用等级:DEBUG(10), INFO(20), SUCCESS(25), WARNING(30), PROGRESS(35), ERROR(40), CRITICAL(50)。
139
- 字符串支持:"DEBUG"、"INFO"、"PROGRESS"、"SUCCESS"、"WARNING"/"WARN"、"ERROR"、"CRITICAL"/"FATAL"(大小写不敏感)。
140
- stream: 输出流,默认 sys.stdout。
141
- message_only: 是否仅输出日志消息(不包含时间与等级),默认 False。
127
+ # 在 network.py
128
+ logger_a = setup_logger("network")
129
+ logger_a.debug("网络调试信息")
130
+
131
+ # 在 db.py
132
+ logger_b = setup_logger("db")
133
+ logger_b.debug("数据库调试信息")
134
+
135
+ 作用:通过不同名称创建的 logger 互不干扰,适合在大型项目中使用。
136
+ 每个 logger 设置不同的日志级别、格式、输出流等。
137
+ level: 日志等级。支持 int 或字符串,默认 "DEBUG"。根据等级过滤后交给 handler 输出。
138
+ 常用等级:DEBUG(10), INFO(20), SUCCESS(25), WARNING(30), PROGRESS(35), ERROR(40), CRITICAL(50)。
139
+ 字符串支持:"DEBUG"、"INFO"、"PROGRESS"、"SUCCESS"、"WARNING"/"WARN"、"ERROR"、"CRITICAL"/"FATAL"(大小写不敏感)。
140
+ stream: 输出流,默认 sys.stdout。
141
+ message_only: 是否仅输出日志消息(不包含时间与等级),默认 False。
142
142
 
143
143
  Returns:
144
144
  配置完成的 logger。示例:
@@ -1,6 +1,6 @@
1
1
  import pandas as pd
2
2
  from decimal import Decimal
3
- from typing import Union, List, Dict, Any, Optional, Literal
3
+ from typing import Any, Literal
4
4
  from pandas import (
5
5
  Int64Dtype,
6
6
  Float64Dtype,
@@ -20,7 +20,7 @@ TYPE_MAPPING = {
20
20
  }
21
21
 
22
22
 
23
- def convert_columns(df: pd.DataFrame, columns: Dict[str, str], decimal_places: Optional[int] = None) -> pd.DataFrame:
23
+ def convert_columns(df: pd.DataFrame, columns: dict[str, str], decimal_places: int | None = None) -> pd.DataFrame:
24
24
  """
25
25
  转换DataFrame中指定列的数据类型。
26
26
 
@@ -58,7 +58,7 @@ def convert_columns(df: pd.DataFrame, columns: Dict[str, str], decimal_places: O
58
58
  def convert_series(
59
59
  data: pd.Series,
60
60
  return_type: Literal["dict", "df", "series"] = "dict"
61
- ) -> Union[Dict[Any, Union[int, float]], pd.DataFrame, pd.Series]:
61
+ ) -> dict[Any, int | float] | pd.DataFrame | pd.Series:
62
62
  """
63
63
  将 pandas Series 格式化为指定的返回类型。
64
64
 
@@ -97,7 +97,7 @@ def convert_series(
97
97
  return data
98
98
 
99
99
 
100
- def convert_numeric_series(series: pd.Series, decimal_places: Optional[int] = None) -> pd.Series:
100
+ def convert_numeric_series(series: pd.Series, decimal_places: int | None = None) -> pd.Series:
101
101
  """
102
102
  将Series转换为数值类型,并自动检测应该使用 int 还是 float。
103
103
  当 decimal_places == 0 时,round后自动转为Int64类型。
@@ -139,9 +139,9 @@ def convert_numeric_series(series: pd.Series, decimal_places: Optional[int] = No
139
139
 
140
140
 
141
141
  def _resolve_columns_decimal_places(
142
- columns: Union[List[str], Dict[str, Optional[int]]],
143
- decimal_places: Optional[int],
144
- ) -> tuple[List[str], Dict[str, Optional[int]]]:
142
+ columns: list[str] | dict[str, int | None],
143
+ decimal_places: int | None,
144
+ ) -> tuple[list[str], dict[str, int | None]]:
145
145
  """
146
146
  解析 columns 参数,返回目标列列表和每列对应的 decimal_places 字典。
147
147
 
@@ -175,7 +175,7 @@ def _resolve_columns_decimal_places(
175
175
 
176
176
  def round_columns(
177
177
  df: pd.DataFrame,
178
- columns: Union[List[str], Dict[str, Optional[int]]],
178
+ columns: list[str] | dict[str, int | None],
179
179
  decimal_places: int = 0,
180
180
  ) -> pd.DataFrame:
181
181
  """
@@ -212,8 +212,8 @@ def _has_decimal(df: pd.DataFrame, column: str) -> bool:
212
212
 
213
213
  def convert_decimal(
214
214
  df: pd.DataFrame,
215
- columns: Union[List[str], Dict[str, Optional[int]], None] = None,
216
- decimal_places: Optional[int] = None,
215
+ columns: list[str] | dict[str, int | None] | None = None,
216
+ decimal_places: int | None = None,
217
217
  ) -> pd.DataFrame:
218
218
  """
219
219
  检测DataFrame中是否包含Decimal类型的字段,如果包含则转换为数值类型。
@@ -255,7 +255,7 @@ def convert_decimal(
255
255
 
256
256
  def load_json(
257
257
  df: pd.DataFrame,
258
- columns: List[str],
258
+ columns: list[str],
259
259
  empty_to_dict: bool = True,
260
260
  ) -> pd.DataFrame:
261
261
  """
@@ -280,7 +280,7 @@ def load_json(
280
280
 
281
281
  def convert_datetime_str(
282
282
  df: pd.DataFrame,
283
- columns: List[str],
283
+ columns: list[str],
284
284
  include_time: bool = True,
285
285
  include_seconds: bool = True,
286
286
  fail_fill: Any = "",
@@ -319,29 +319,23 @@ def convert_datetime_str(
319
319
 
320
320
 
321
321
  def convert_str_datetime(
322
- df: pd.DataFrame, columns: List[str]
322
+ df: pd.DataFrame, columns: list[str]
323
323
  ) -> pd.DataFrame:
324
324
  """
325
325
  将DataFrame中指定字符串列转换为datetime类型。
326
326
 
327
327
  参数:
328
328
  - df (pd.DataFrame):输入的DataFrame。
329
- - columns (List[str]):要转换为datetime类型的列名列表。
329
+ - columns (list[str]):要转换为datetime类型的列名列表。
330
330
 
331
331
  返回:
332
332
  - pd.DataFrame:转换后的DataFrame。
333
333
  """
334
- def parse_datetime(value: str) -> Union[pd.Timestamp, str]:
335
- try:
336
- return pd.to_datetime(value)
337
- except (ValueError, TypeError):
338
- return value
339
-
340
334
  for column in columns:
341
335
  if column not in df.columns:
342
336
  continue
343
337
 
344
- df[column] = df[column].apply(parse_datetime)
338
+ df[column] = pd.to_datetime(df[column], errors="coerce")
345
339
 
346
340
  return df
347
341
 
@@ -1,14 +1,14 @@
1
1
  import pandas as pd
2
2
  from datetime import datetime
3
3
  from pandas.api.types import is_datetime64_any_dtype, is_timedelta64_dtype
4
- from typing import Union, List, Any, Optional
4
+ from typing import Any
5
5
 
6
6
 
7
7
  # 单位 -> 对应秒数
8
8
  _UNIT_SECONDS = {"s": 1, "m": 60, "h": 3600, "d": 86400}
9
9
 
10
10
 
11
- def _resolve_date(df: pd.DataFrame, date: Union[str, datetime]) -> pd.Series:
11
+ def _resolve_date(df: pd.DataFrame, date: str | datetime) -> pd.Series:
12
12
  """将列名或 datetime 统一转换为 pd.Series"""
13
13
  if isinstance(date, datetime):
14
14
  return pd.Series([date] * len(df), index=df.index)
@@ -21,7 +21,7 @@ def _resolve_date(df: pd.DataFrame, date: Union[str, datetime]) -> pd.Series:
21
21
 
22
22
 
23
23
  def fill_nat(
24
- df: pd.DataFrame, columns: Union[List[str], str], fill_value: Any = ""
24
+ df: pd.DataFrame, columns: list[str] | str, fill_value: Any = ""
25
25
  ) -> pd.DataFrame:
26
26
  """将指定列的 NaT(Datetime/Timedelta)替换为空字符串"""
27
27
 
@@ -45,11 +45,11 @@ def fill_nat(
45
45
  def cal_date_diff(
46
46
  df: pd.DataFrame,
47
47
  target_column: str,
48
- old_date: Union[str, datetime],
49
- new_date: Union[str, datetime],
48
+ old_date: str | datetime,
49
+ new_date: str | datetime,
50
50
  unit: str = "h",
51
51
  decimal_places: int = 1,
52
- nat: Optional[int] = None
52
+ nat: int | None = None
53
53
  ) -> pd.DataFrame:
54
54
  """
55
55
  计算DataFrame中两列日期的时间差,并将结果填充到指定列。
@@ -1,14 +1,15 @@
1
1
  import pandas as pd
2
2
  from pandas.api.types import is_datetime64_any_dtype, is_timedelta64_dtype
3
- from typing import Union, List, Any, Dict, Optional
3
+ from typing import Any
4
4
  from .convert_utils import convert_numeric_series
5
5
 
6
6
 
7
7
  def fill_na(
8
8
  df: pd.DataFrame,
9
- columns: Union[List[str], Dict[str, Any]],
9
+ columns: list[str] | dict[str, Any],
10
10
  fill_value: Any = "",
11
- decimal_places: Optional[int] = None,
11
+ decimal_places: int | None = None,
12
+ copy: bool = False,
12
13
  ) -> pd.DataFrame:
13
14
  """
14
15
  替换DataFrame中指定列的空值为指定值。
@@ -21,13 +22,16 @@ def fill_na(
21
22
  * 如果为List[str]且为空列表,则对所有列应用fill_value填充
22
23
  - fill_value (Any, optional):当columns为列表时的默认填充值,默认为空字符串。
23
24
  - decimal_places (int, optional):当进行数值转换时保留的小数位数,默认为None表示不限制
25
+ - copy (bool, optional):是否在副本上操作而非修改原 DataFrame,默认 False。
24
26
 
25
27
  返回:
26
28
  - pd.DataFrame:替换空值后的DataFrame。
27
29
 
28
- 注意:本函数直接修改传入的 DataFrame,不返回副本。如需保留原数据,
29
- 请在调用前使用 df.copy()。
30
+ 注意:当 copy=False 时,本函数直接修改传入的 DataFrame
31
+ 如需保留原数据,请设置 copy=True 或在调用前使用 df.copy()。
30
32
  """
33
+ if copy:
34
+ df = df.copy()
31
35
  _is_numeric_fill = isinstance(fill_value, (int, float))
32
36
 
33
37
  if isinstance(columns, list):
@@ -1,12 +1,11 @@
1
1
  import pandas as pd
2
- from typing import Union, List, Tuple
3
2
 
4
3
  from .statistics.mask_utils import build_single_mask, build_base_mask, combine_masks
5
4
 
6
5
 
7
6
  def pd_filter(
8
7
  df: pd.DataFrame,
9
- conditions: Union[Tuple, List[Tuple], List[pd.Series]],
8
+ conditions: tuple | list[tuple] | list[pd.Series],
10
9
  logic: str = "and"
11
10
  ) -> pd.DataFrame:
12
11
  """
@@ -6,7 +6,7 @@ sum、mean、max、min、count、median、std、var等聚合计算。
6
6
  """
7
7
 
8
8
  import pandas as pd
9
- from typing import Any, Dict, Optional, Union, List, Tuple, Literal
9
+ from typing import Any, Literal
10
10
  from .mask_utils import build_single_mask, build_base_mask
11
11
  from ..convert_utils import convert_series
12
12
 
@@ -16,13 +16,13 @@ def group_agg(
16
16
  group_col: str,
17
17
  agg_col: str,
18
18
  agg_func: str = "sum",
19
- sort: Optional[str] = None,
20
- conditions: Optional[Union[Tuple, List[Tuple]]] = None,
19
+ sort: str | None = None,
20
+ conditions: tuple | list[tuple] | None = None,
21
21
  logic: str = "and",
22
- true_mask: Optional[pd.Series] = None,
23
- false_mask: Optional[pd.Series] = None,
22
+ true_mask: pd.Series | None = None,
23
+ false_mask: pd.Series | None = None,
24
24
  return_type: Literal["dict", "df", "series"] = "dict"
25
- ) -> Union[Dict[Any, Union[int, float]], pd.DataFrame, pd.Series]:
25
+ ) -> dict[Any, int | float] | pd.DataFrame | pd.Series:
26
26
  """
27
27
  按指定列分组,对另一列进行聚合统计。
28
28
 
@@ -1,5 +1,6 @@
1
1
  import pandas as pd
2
- from typing import List, Tuple, Union, Optional, Dict, Any, Mapping, Literal
2
+ from collections.abc import Mapping
3
+ from typing import Any, Literal
3
4
  from .mask_utils import build_single_mask, build_base_mask
4
5
  from ..convert_utils import convert_series
5
6
 
@@ -8,14 +9,14 @@ def value_counts(
8
9
  df: pd.DataFrame,
9
10
  column: str,
10
11
  mode: str = "count",
11
- sort: Optional[str] = None,
12
+ sort: str | None = None,
12
13
  dropna: bool = True,
13
- conditions: Optional[Union[Tuple, List[Tuple]]] = None,
14
+ conditions: tuple | list[tuple] | None = None,
14
15
  logic: str = "and",
15
- true_mask: Optional[pd.Series] = None,
16
- false_mask: Optional[pd.Series] = None,
16
+ true_mask: pd.Series | None = None,
17
+ false_mask: pd.Series | None = None,
17
18
  return_type: Literal["dict", "df", "series"] = "dict"
18
- ) -> Union[Mapping[Any, Union[int, float]], pd.DataFrame, pd.Series]:
19
+ ) -> Mapping[Any, int | float] | pd.DataFrame | pd.Series:
19
20
  """
20
21
  统计DataFrame指定列中不同值的计数数据。
21
22
 
@@ -84,10 +85,10 @@ def value_counts(
84
85
 
85
86
  def count(
86
87
  df: pd.DataFrame,
87
- conditions: Union[Tuple, List[Tuple]],
88
+ conditions: tuple | list[tuple],
88
89
  logic: str = "and",
89
- true_mask: Optional[pd.Series] = None,
90
- false_mask: Optional[pd.Series] = None
90
+ true_mask: pd.Series | None = None,
91
+ false_mask: pd.Series | None = None
91
92
  ) -> int:
92
93
  """
93
94
  使用int sum 方法,统计DataFrame中符合条件的非空值数量。
@@ -1,5 +1,6 @@
1
1
  import pandas as pd
2
- from typing import Any, Dict, List, Tuple, Union, Optional, Mapping, Literal
2
+ from collections.abc import Mapping
3
+ from typing import Any, Literal
3
4
  from .mask_utils import build_single_mask as _original_build_single_mask, build_base_mask as _original_build_base_mask, combine_masks
4
5
  from .count_utils import count as _original_count, value_counts as _original_value_counts
5
6
  from .agg_utils import group_agg as _original_group_agg
@@ -32,9 +33,6 @@ class DataFrameStatistics:
32
33
  self._false_mask = None
33
34
  self._reset_base_masks()
34
35
 
35
- # 方法继承
36
- self.combine_masks = combine_masks
37
-
38
36
 
39
37
  # 重置 true_mask 和 false_mask
40
38
  def _reset_base_masks(self):
@@ -47,9 +45,16 @@ class DataFrameStatistics:
47
45
  if self._true_mask is not None and len(self._true_mask) != len(self._df):
48
46
  self._reset_base_masks()
49
47
 
50
- def build_base_mask(self, conditions: List[Tuple], logic: str = "and",
51
- true_mask: Optional[pd.Series] = None,
52
- false_mask: Optional[pd.Series] = None) -> pd.Series:
48
+ def combine_masks(self, masks: list[pd.Series], logic: str = "and") -> pd.Series:
49
+ """
50
+ 合并多个布尔掩码,支持复杂的嵌套逻辑组合(委托给模块级 combine_masks 函数)
51
+ """
52
+ return combine_masks(masks, logic)
53
+
54
+
55
+ def build_base_mask(self, conditions: list[tuple], logic: str = "and",
56
+ true_mask: pd.Series | None = None,
57
+ false_mask: pd.Series | None = None) -> pd.Series:
53
58
  """
54
59
  构建基础查询条件掩码,自动使用内部掩码参数
55
60
 
@@ -71,7 +76,7 @@ class DataFrameStatistics:
71
76
  return _original_build_base_mask(self._df, conditions, logic, true_mask, false_mask)
72
77
 
73
78
 
74
- def build_single_mask(self, condition: Tuple) -> pd.Series:
79
+ def build_single_mask(self, condition: tuple) -> pd.Series:
75
80
  """
76
81
  构建单个掩码,用于简单条件判断,自动使用内部DataFrame
77
82
 
@@ -86,9 +91,9 @@ class DataFrameStatistics:
86
91
  return _original_build_single_mask(self._df, condition)
87
92
 
88
93
 
89
- def count(self, conditions: Union[Tuple, List[Tuple]], logic: str = "and",
90
- true_mask: Optional[pd.Series] = None,
91
- false_mask: Optional[pd.Series] = None) -> int:
94
+ def count(self, conditions: tuple | list[tuple], logic: str = "and",
95
+ true_mask: pd.Series | None = None,
96
+ false_mask: pd.Series | None = None) -> int:
92
97
  """
93
98
  统计DataFrame中符合条件的非空值数量,自动使用内部掩码参数
94
99
 
@@ -114,14 +119,14 @@ class DataFrameStatistics:
114
119
  self,
115
120
  column: str,
116
121
  mode: str = "count",
117
- sort: Optional[str] = None,
122
+ sort: str | None = None,
118
123
  dropna: bool = True,
119
- conditions: Optional[Union[Tuple, List[Tuple]]] = None,
124
+ conditions: tuple | list[tuple] | None = None,
120
125
  logic: str = "and",
121
- true_mask: Optional[pd.Series] = None,
122
- false_mask: Optional[pd.Series] = None,
126
+ true_mask: pd.Series | None = None,
127
+ false_mask: pd.Series | None = None,
123
128
  return_type: Literal["dict", "df", "series"] = "dict"
124
- ) -> Union[Mapping[Any, Union[int, float]], pd.DataFrame, pd.Series]:
129
+ ) -> Mapping[Any, int | float] | pd.DataFrame | pd.Series:
125
130
  """
126
131
  统计指定列中不同值的计数数据,自动使用内部掩码参数
127
132
 
@@ -170,13 +175,13 @@ class DataFrameStatistics:
170
175
  group_col: str,
171
176
  agg_col: str,
172
177
  agg_func: str = "sum",
173
- sort: Optional[str] = None,
174
- conditions: Optional[Union[Tuple, List[Tuple]]] = None,
178
+ sort: str | None = None,
179
+ conditions: tuple | list[tuple] | None = None,
175
180
  logic: str = "and",
176
- true_mask: Optional[pd.Series] = None,
177
- false_mask: Optional[pd.Series] = None,
181
+ true_mask: pd.Series | None = None,
182
+ false_mask: pd.Series | None = None,
178
183
  return_type: Literal["dict", "df", "series"] = "dict"
179
- ) -> Union[Dict[Any, Union[int, float]], pd.DataFrame, pd.Series]:
184
+ ) -> dict[Any, int | float] | pd.DataFrame | pd.Series:
180
185
  """
181
186
  按指定列分组,对另一列进行聚合统计,自动使用内部掩码参数
182
187
 
@@ -217,7 +222,7 @@ class DataFrameStatistics:
217
222
  )
218
223
 
219
224
 
220
- def dataframe_info(self) -> Dict[str, Any]:
225
+ def dataframe_info(self) -> dict[str, Any]:
221
226
  """获取DataFrame的基本信息"""
222
227
  return {
223
228
  "shape": self._df.shape,
@@ -1,7 +1,5 @@
1
1
  import numpy as np
2
2
  import pandas as pd
3
- from typing import List, Tuple, Optional
4
-
5
3
 
6
4
 
7
5
  def _is_empty(x):
@@ -40,7 +38,7 @@ def _is_not_empty(x):
40
38
  return True
41
39
 
42
40
 
43
- def build_single_mask(df: pd.DataFrame, condition: Tuple) -> pd.Series:
41
+ def build_single_mask(df: pd.DataFrame, condition: tuple) -> pd.Series:
44
42
  """
45
43
  构建单个掩码,用于简单条件判断
46
44
 
@@ -121,10 +119,10 @@ def build_single_mask(df: pd.DataFrame, condition: Tuple) -> pd.Series:
121
119
 
122
120
  def build_base_mask(
123
121
  df: pd.DataFrame,
124
- conditions: List[Tuple],
122
+ conditions: list[tuple],
125
123
  logic: str = "and",
126
- true_mask: Optional[pd.Series] = None,
127
- false_mask: Optional[pd.Series] = None,
124
+ true_mask: pd.Series | None = None,
125
+ false_mask: pd.Series | None = None,
128
126
  ) -> pd.Series:
129
127
  """
130
128
  构建基础查询条件掩码
@@ -145,20 +143,26 @@ def build_base_mask(
145
143
  说明:
146
144
  - 如需复杂的嵌套逻辑组合,请使用 combine_masks 方法。
147
145
  """
146
+ def _and_mask(m, c):
147
+ return m & c
148
+
149
+ def _or_mask(m, c):
150
+ return m | c
151
+
148
152
  if logic == "and":
149
153
  mask = (
150
154
  true_mask
151
155
  if true_mask is not None
152
156
  else pd.Series([True] * len(df), index=df.index)
153
157
  )
154
- operator_func = lambda m, c: m & c
158
+ operator_func = _and_mask
155
159
  elif logic == "or":
156
160
  mask = (
157
161
  false_mask
158
162
  if false_mask is not None
159
163
  else pd.Series([False] * len(df), index=df.index)
160
164
  )
161
- operator_func = lambda m, c: m | c
165
+ operator_func = _or_mask
162
166
  else:
163
167
  raise ValueError(f"不支持的逻辑操作类型: {logic},支持 'and' 或 'or'")
164
168
 
@@ -169,7 +173,7 @@ def build_base_mask(
169
173
  return mask
170
174
 
171
175
 
172
- def combine_masks(masks: List[pd.Series], logic: str = "and") -> pd.Series:
176
+ def combine_masks(masks: list[pd.Series], logic: str = "and") -> pd.Series:
173
177
  """
174
178
  合并多个布尔掩码,支持复杂的嵌套逻辑组合
175
179
 
@@ -2,9 +2,10 @@ import json
2
2
  import base64
3
3
  import hashlib
4
4
  import requests
5
+ import urllib.parse
5
6
  from curl_cffi import requests as cffi_requests
6
7
 
7
- from typing import Optional, Dict, Any, Union, Literal
8
+ from typing import Any, Literal
8
9
  from .core import retry_function
9
10
  from .data_models import RequestLog
10
11
 
@@ -14,7 +15,7 @@ HttpMethod = Literal["GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "TRACE",
14
15
 
15
16
  # impersonate 名称 → 对应真实浏览器 User-Agent 映射
16
17
  # 只维护 curl_cffi 支持的主流标识
17
- _IMPERSONATE_UA_MAP: Dict[str, str] = {
18
+ _IMPERSONATE_UA_MAP: dict[str, str] = {
18
19
  "chrome123": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
19
20
  "chrome124": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
20
21
  "safari15_5": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Safari/605.1.15",
@@ -54,9 +55,9 @@ def encode_basic_auth(username: str, password: str) -> str:
54
55
  def curl_cffi_request(
55
56
  method: HttpMethod,
56
57
  url: str,
57
- req_kwargs: Dict[str, Any],
58
+ req_kwargs: dict[str, Any],
58
59
  impersonate: str,
59
- auto_retry: Optional[Dict[str, Any]] = None,
60
+ auto_retry: dict[str, Any] | None = None,
60
61
  ) -> Any:
61
62
  """
62
63
  使用 curl_cffi 发起请求的内部封装。
@@ -105,16 +106,17 @@ def curl_cffi_request(
105
106
  def send_request(
106
107
  method: HttpMethod,
107
108
  url: str,
108
- headers: Optional[Dict[str, str]] = None,
109
- data: Optional[Any] = None,
109
+ headers: dict[str, str] | None = None,
110
+ data: Any | None = None,
111
+ params: dict[str, Any] | None = None,
110
112
  return_type: str = "json",
111
113
  timeout: int = 60,
112
- auto_retry: Optional[Dict[str, Any]] = None,
114
+ auto_retry: dict[str, Any] | None = None,
113
115
  request_log: RequestLog = RequestLog(),
114
116
  curl_fallback: bool = False,
115
117
  curl_fallback_impersonate: str = _DEFAULT_IMPERSONATE,
116
118
  stream: bool = False,
117
- ) -> Union[Dict, str, requests.Response]:
119
+ ) -> dict | str | requests.Response:
118
120
  """
119
121
  发送HTTP请求的通用函数
120
122
 
@@ -122,6 +124,7 @@ def send_request(
122
124
  :param url: 请求URL
123
125
  :param headers: 请求头
124
126
  :param data: 请求数据
127
+ :param params: URL查询参数,dict 类型,会自动进行 URL 编码并拼接在 URL 后
125
128
  :param return_type: 返回类型(json, text, response)
126
129
  :param timeout: 请求超时时间
127
130
  :param auto_retry: 自动重试配置,格式为:
@@ -139,6 +142,10 @@ def send_request(
139
142
  """
140
143
  payload = None
141
144
 
145
+ # 处理 URL 查询参数
146
+ parameters = urllib.parse.urlencode(params) if params else ""
147
+ url = url + (f"?{parameters}" if parameters else "")
148
+
142
149
  if data is not None:
143
150
  if isinstance(data, dict) or isinstance(data, list):
144
151
  payload = json.dumps(data, ensure_ascii=False)
@@ -221,7 +228,7 @@ def send_request(
221
228
  # 检查URL是否有效
222
229
  def check_url_valid(
223
230
  url: str,
224
- headers: Optional[Dict[str, str]] = None,
231
+ headers: dict[str, str] | None = None,
225
232
  max_retries: int = 3,
226
233
  curl_fallback: bool = False,
227
234
  curl_fallback_impersonate: str = _DEFAULT_IMPERSONATE,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: funcguard
3
- Version: 0.2.55
3
+ Version: 0.2.56
4
4
  Summary: FuncGuard是一个Python库,提供函数执行超时控制、重试机制、HTTP请求封装和格式化打印工具。
5
5
  Home-page: https://github.com/tinycen/funcguard
6
6
  Author: tinycen
@@ -188,6 +188,15 @@ response = send_request(
188
188
  )
189
189
  print(response)
190
190
 
191
+ # 带 URL 查询参数的 GET 请求
192
+ response = send_request(
193
+ method="GET",
194
+ url="https://api.example.com/users",
195
+ params={"page": 1, "size": 20, "search": "张三"}
196
+ # 等价于: https://api.example.com/users?page=1&size=20&search=%E5%BC%A0%E4%B8%89
197
+ )
198
+ print(response)
199
+
191
200
  # POST 请求(自动将 dict/list 转为 JSON)
192
201
  response = send_request(
193
202
  method="POST",
@@ -9,7 +9,7 @@ except FileNotFoundError:
9
9
 
10
10
  setup(
11
11
  name='funcguard',
12
- version='0.2.55',
12
+ version='0.2.56',
13
13
  packages=find_packages(),
14
14
  install_requires=[
15
15
  'requests',
@@ -1,12 +0,0 @@
1
- from dataclasses import dataclass
2
-
3
- from typing import Optional
4
-
5
- @dataclass
6
- class RequestLog:
7
- save_method: Optional[bool] = True
8
- save_url: Optional[bool] = True
9
- save_headers: Optional[bool] = True
10
- save_body: Optional[bool] = True
11
- save_response: Optional[bool] = True
12
- save_path: Optional[str] = ""
File without changes
File without changes
File without changes
File without changes