xiaoyi-utils 0.1.3__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.
@@ -0,0 +1,68 @@
1
+ Metadata-Version: 2.5
2
+ Name: xiaoyi-utils
3
+ Version: 0.1.3
4
+ Summary: 一个用于学习 Python 公共库构建和发布的示例工程
5
+ Requires-Python: >=3.14
6
+ Requires-Dist: markdown>=3.5
7
+ Requires-Dist: python-dateutil>=2.8
8
+ Description-Content-Type: text/markdown
9
+
10
+ # xiaoyi-utils
11
+
12
+ 一个用于学习 Python 公共库构建和发布的示例工程,提供常用工具函数。
13
+
14
+ ## 安装
15
+
16
+ ```bash
17
+ pip install -e .
18
+ ```
19
+
20
+ ## 模块说明
21
+
22
+ ### date — 日期时间
23
+
24
+ ```python
25
+ from xiaoyi_utils.date import parse_date, format_datetime, add_days, diff_days
26
+
27
+ dt = parse_date("2026-09-03")
28
+ print(format_datetime(dt, "%Y年%m月%d日"))
29
+ print(add_days(dt, 7))
30
+ print(diff_days("2026-01-01", "2026-09-03"))
31
+ ```
32
+
33
+ ### markdown — Markdown 处理
34
+
35
+ ```python
36
+ from xiaoyi_utils.markdown import md_to_html, md_to_plain
37
+
38
+ html = md_to_html("# Hello **world**")
39
+ plain = md_to_plain("# Hello **world**")
40
+ ```
41
+
42
+ ### text — 文本处理
43
+
44
+ ```python
45
+ from xiaoyi_utils.text import truncate, slugify, camel_to_snake
46
+
47
+ print(truncate("这是一段很长的文本", 6))
48
+ print(slugify("Hello World 你好"))
49
+ print(camel_to_snake("UserProfile"))
50
+ ```
51
+
52
+ ### hash — 哈希摘要
53
+
54
+ ```python
55
+ from xiaoyi_utils.hash import md5, sha256, file_hash
56
+
57
+ print(sha256("hello"))
58
+ print(file_hash("README.md"))
59
+ ```
60
+
61
+ ### json_util — JSON 工具
62
+
63
+ ```python
64
+ from xiaoyi_utils.json_util import safe_loads, pretty_dumps, read_json, write_json
65
+
66
+ data = safe_loads('{"name": "xiaoyi"}', default={})
67
+ print(pretty_dumps(data))
68
+ ```
@@ -0,0 +1,59 @@
1
+ # xiaoyi-utils
2
+
3
+ 一个用于学习 Python 公共库构建和发布的示例工程,提供常用工具函数。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ pip install -e .
9
+ ```
10
+
11
+ ## 模块说明
12
+
13
+ ### date — 日期时间
14
+
15
+ ```python
16
+ from xiaoyi_utils.date import parse_date, format_datetime, add_days, diff_days
17
+
18
+ dt = parse_date("2026-09-03")
19
+ print(format_datetime(dt, "%Y年%m月%d日"))
20
+ print(add_days(dt, 7))
21
+ print(diff_days("2026-01-01", "2026-09-03"))
22
+ ```
23
+
24
+ ### markdown — Markdown 处理
25
+
26
+ ```python
27
+ from xiaoyi_utils.markdown import md_to_html, md_to_plain
28
+
29
+ html = md_to_html("# Hello **world**")
30
+ plain = md_to_plain("# Hello **world**")
31
+ ```
32
+
33
+ ### text — 文本处理
34
+
35
+ ```python
36
+ from xiaoyi_utils.text import truncate, slugify, camel_to_snake
37
+
38
+ print(truncate("这是一段很长的文本", 6))
39
+ print(slugify("Hello World 你好"))
40
+ print(camel_to_snake("UserProfile"))
41
+ ```
42
+
43
+ ### hash — 哈希摘要
44
+
45
+ ```python
46
+ from xiaoyi_utils.hash import md5, sha256, file_hash
47
+
48
+ print(sha256("hello"))
49
+ print(file_hash("README.md"))
50
+ ```
51
+
52
+ ### json_util — JSON 工具
53
+
54
+ ```python
55
+ from xiaoyi_utils.json_util import safe_loads, pretty_dumps, read_json, write_json
56
+
57
+ data = safe_loads('{"name": "xiaoyi"}', default={})
58
+ print(pretty_dumps(data))
59
+ ```
@@ -0,0 +1,22 @@
1
+ # pyproject.toml
2
+
3
+ [build-system] # 构建系统配置,指定构建工具和依赖
4
+ requires = ["hatchling"] # 依赖的后端构建工具
5
+ build-backend = "hatchling.build" # 使用哪个工具的哪个模块来构建项目
6
+
7
+ [project] # 工程描述
8
+ name = "xiaoyi-utils" # 发行版名称
9
+ version = "0.1.3" # 版本号,遵循语义化版本控制
10
+ description = "一个用于学习 Python 公共库构建和发布的示例工程" # 项目简介
11
+ requires-python = ">=3.14" # 指定支持的 Python 版本
12
+ readme = "README.md" # 指定项目的 README 文件路径
13
+ dependencies = [
14
+ "python-dateutil>=2.8", # 依赖的第三方库,指定版本要求
15
+ "Markdown>=3.5", # 另一个依赖
16
+ ]
17
+
18
+ [tool.hatch.build.targets.sdist] # 配置源代码分发包
19
+ # 配置留空,表示它会默认包含所有项目文件
20
+
21
+ [tool.hatch.build.targets.wheel] # 配置 wheel 包
22
+ packages = ["src/xiaoyi_utils"] # 配置 wheel 包的构建目标,指定包含的包路径
@@ -0,0 +1,5 @@
1
+ """xiaoyi-utils:常用 Python 工具函数集合。"""
2
+
3
+ __version__ = "0.1.3"
4
+
5
+ __all__ = ["__version__"]
@@ -0,0 +1,23 @@
1
+ """日期与时间工具模块。"""
2
+
3
+ from xiaoyi_utils.date.core import (
4
+ add_days,
5
+ add_months,
6
+ diff_days,
7
+ format_datetime,
8
+ is_weekend,
9
+ now,
10
+ parse_date,
11
+ today,
12
+ )
13
+
14
+ __all__ = [
15
+ "add_days",
16
+ "add_months",
17
+ "diff_days",
18
+ "format_datetime",
19
+ "is_weekend",
20
+ "now",
21
+ "parse_date",
22
+ "today",
23
+ ]
@@ -0,0 +1,83 @@
1
+ """日期与时间相关工具。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import date, datetime, timedelta, timezone
6
+ from typing import overload
7
+
8
+ from dateutil import parser as date_parser
9
+ from dateutil.relativedelta import relativedelta
10
+
11
+
12
+ def now(tz: timezone | None = None) -> datetime:
13
+ """返回当前时间,默认本地时区。"""
14
+ return datetime.now(tz)
15
+
16
+
17
+ def today() -> date:
18
+ """返回今天的日期。"""
19
+ return date.today()
20
+
21
+
22
+ def parse_date(value: str | datetime | date, *, dayfirst: bool = False) -> datetime:
23
+ """解析多种格式的日期字符串,返回 datetime。"""
24
+ if isinstance(value, datetime):
25
+ return value
26
+ if isinstance(value, date):
27
+ return datetime(value.year, value.month, value.day)
28
+ return date_parser.parse(value, dayfirst=dayfirst)
29
+
30
+
31
+ def format_datetime(
32
+ value: datetime | date | str,
33
+ fmt: str = "%Y-%m-%d %H:%M:%S",
34
+ *,
35
+ dayfirst: bool = False,
36
+ ) -> str:
37
+ """格式化日期/时间为字符串。"""
38
+ dt = parse_date(value, dayfirst=dayfirst) if isinstance(value, str) else value
39
+ if isinstance(dt, date) and not isinstance(dt, datetime):
40
+ dt = datetime(dt.year, dt.month, dt.day)
41
+ return dt.strftime(fmt)
42
+
43
+
44
+ def add_days(value: datetime | date | str, days: int, *, dayfirst: bool = False) -> datetime:
45
+ """在指定日期上增加天数。"""
46
+ dt = parse_date(value, dayfirst=dayfirst) if isinstance(value, str) else value
47
+ if isinstance(dt, date) and not isinstance(dt, datetime):
48
+ dt = datetime(dt.year, dt.month, dt.day)
49
+ return dt + timedelta(days=days)
50
+
51
+
52
+ def add_months(value: datetime | date | str, months: int, *, dayfirst: bool = False) -> datetime:
53
+ """在指定日期上增加月数。"""
54
+ dt = parse_date(value, dayfirst=dayfirst) if isinstance(value, str) else value
55
+ if isinstance(dt, date) and not isinstance(dt, datetime):
56
+ dt = datetime(dt.year, dt.month, dt.day)
57
+ return dt + relativedelta(months=months)
58
+
59
+
60
+ @overload
61
+ def diff_days(start: datetime | date | str, end: datetime | date | str, *, dayfirst: bool = False) -> int: ...
62
+
63
+
64
+ def diff_days(
65
+ start: datetime | date | str,
66
+ end: datetime | date | str,
67
+ *,
68
+ dayfirst: bool = False,
69
+ ) -> int:
70
+ """计算两个日期之间的天数差(end - start)。"""
71
+ start_dt = parse_date(start, dayfirst=dayfirst) if isinstance(start, str) else start
72
+ end_dt = parse_date(end, dayfirst=dayfirst) if isinstance(end, str) else end
73
+
74
+ start_date = start_dt.date() if isinstance(start_dt, datetime) else start_dt
75
+ end_date = end_dt.date() if isinstance(end_dt, datetime) else end_dt
76
+ return (end_date - start_date).days
77
+
78
+
79
+ def is_weekend(value: datetime | date | str, *, dayfirst: bool = False) -> bool:
80
+ """判断是否为周末(周六或周日)。"""
81
+ dt = parse_date(value, dayfirst=dayfirst) if isinstance(value, str) else value
82
+ target = dt.date() if isinstance(dt, datetime) else dt
83
+ return target.weekday() >= 5
@@ -0,0 +1,5 @@
1
+ """哈希工具模块。"""
2
+
3
+ from xiaoyi_utils.hash.core import file_hash, md5, sha256
4
+
5
+ __all__ = ["file_hash", "md5", "sha256"]
@@ -0,0 +1,33 @@
1
+ """哈希与摘要工具。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ from pathlib import Path
7
+
8
+
9
+ def _digest(data: bytes, algorithm: str) -> str:
10
+ hasher = hashlib.new(algorithm)
11
+ hasher.update(data)
12
+ return hasher.hexdigest()
13
+
14
+
15
+ def md5(text: str | bytes) -> str:
16
+ """计算 MD5 十六进制摘要。"""
17
+ data = text.encode("utf-8") if isinstance(text, str) else text
18
+ return _digest(data, "md5")
19
+
20
+
21
+ def sha256(text: str | bytes) -> str:
22
+ """计算 SHA256 十六进制摘要。"""
23
+ data = text.encode("utf-8") if isinstance(text, str) else text
24
+ return _digest(data, "sha256")
25
+
26
+
27
+ def file_hash(path: str | Path, *, algorithm: str = "sha256", chunk_size: int = 8192) -> str:
28
+ """计算文件的哈希值。"""
29
+ hasher = hashlib.new(algorithm)
30
+ with Path(path).open("rb") as fp:
31
+ while chunk := fp.read(chunk_size):
32
+ hasher.update(chunk)
33
+ return hasher.hexdigest()
@@ -0,0 +1,5 @@
1
+ """JSON 工具模块。"""
2
+
3
+ from xiaoyi_utils.json_util.core import pretty_dumps, read_json, safe_loads, write_json
4
+
5
+ __all__ = ["pretty_dumps", "read_json", "safe_loads", "write_json"]
@@ -0,0 +1,45 @@
1
+ """JSON 读写与格式化工具。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+ from typing import Any, TypeVar
8
+
9
+ T = TypeVar("T")
10
+
11
+
12
+ def safe_loads(text: str, default: T | None = None) -> Any | T:
13
+ """安全解析 JSON 字符串,失败时返回 default。"""
14
+ try:
15
+ return json.loads(text)
16
+ except (json.JSONDecodeError, TypeError):
17
+ return default
18
+
19
+
20
+ def pretty_dumps(data: Any, *, indent: int = 2, ensure_ascii: bool = False) -> str:
21
+ """格式化输出 JSON 字符串。"""
22
+ return json.dumps(data, indent=indent, ensure_ascii=ensure_ascii, default=str)
23
+
24
+
25
+ def read_json(path: str | Path, *, default: T | None = None) -> Any | T:
26
+ """读取 JSON 文件,失败时返回 default。"""
27
+ try:
28
+ with Path(path).open("r", encoding="utf-8") as fp:
29
+ return json.load(fp)
30
+ except (OSError, json.JSONDecodeError):
31
+ return default
32
+
33
+
34
+ def write_json(
35
+ path: str | Path,
36
+ data: Any,
37
+ *,
38
+ indent: int = 2,
39
+ ensure_ascii: bool = False,
40
+ ) -> None:
41
+ """写入 JSON 文件。"""
42
+ target = Path(path)
43
+ target.parent.mkdir(parents=True, exist_ok=True)
44
+ with target.open("w", encoding="utf-8") as fp:
45
+ json.dump(data, fp, indent=indent, ensure_ascii=ensure_ascii, default=str)
@@ -0,0 +1,5 @@
1
+ """Markdown 工具模块。"""
2
+
3
+ from xiaoyi_utils.markdown.core import is_markdown, md_to_html, md_to_plain
4
+
5
+ __all__ = ["is_markdown", "md_to_html", "md_to_plain"]
@@ -0,0 +1,34 @@
1
+ """Markdown 相关工具。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+
7
+ import markdown
8
+
9
+
10
+ def md_to_html(text: str, *, extensions: list[str] | None = None) -> str:
11
+ """将 Markdown 文本转换为 HTML。"""
12
+ ext = extensions or ["extra", "codehilite"]
13
+ return markdown.markdown(text, extensions=ext)
14
+
15
+
16
+ def md_to_plain(text: str) -> str:
17
+ """去除 Markdown 标记,提取纯文本。"""
18
+ html = md_to_html(text, extensions=[])
19
+ plain = re.sub(r"<[^>]+>", "", html)
20
+ plain = re.sub(r"\s+", " ", plain)
21
+ return plain.strip()
22
+
23
+
24
+ def is_markdown(text: str) -> bool:
25
+ """简单判断文本是否包含常见 Markdown 语法。"""
26
+ patterns = (
27
+ r"^#{1,6}\s", # 标题
28
+ r"\*\*.+\*\*", # 粗体
29
+ r"\[.+\]\(.+\)", # 链接
30
+ r"^\s*[-*+]\s", # 无序列表
31
+ r"^\s*\d+\.\s", # 有序列表
32
+ r"```", # 代码块
33
+ )
34
+ return any(re.search(pattern, text, re.MULTILINE) for pattern in patterns)
@@ -0,0 +1,19 @@
1
+ """文本处理工具模块。"""
2
+
3
+ from xiaoyi_utils.text.core import (
4
+ camel_to_snake,
5
+ is_blank,
6
+ remove_whitespace,
7
+ slugify,
8
+ snake_to_camel,
9
+ truncate,
10
+ )
11
+
12
+ __all__ = [
13
+ "camel_to_snake",
14
+ "is_blank",
15
+ "remove_whitespace",
16
+ "slugify",
17
+ "snake_to_camel",
18
+ "truncate",
19
+ ]
@@ -0,0 +1,53 @@
1
+ """文本处理工具。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ import unicodedata
7
+
8
+
9
+ def truncate(text: str, length: int, *, suffix: str = "...") -> str:
10
+ """截断文本,超出部分用后缀代替。"""
11
+ if length <= 0:
12
+ return ""
13
+ if len(text) <= length:
14
+ return text
15
+ if len(suffix) >= length:
16
+ return suffix[:length]
17
+ return text[: length - len(suffix)] + suffix
18
+
19
+
20
+ def slugify(text: str, *, separator: str = "-") -> str:
21
+ """将文本转为 URL 友好的 slug。"""
22
+ normalized = unicodedata.normalize("NFKD", text)
23
+ ascii_text = normalized.encode("ascii", "ignore").decode("ascii")
24
+ slug = re.sub(r"[^\w\s-]", "", ascii_text.lower())
25
+ slug = re.sub(r"[\s_-]+", separator, slug).strip(separator)
26
+ return slug
27
+
28
+
29
+ def camel_to_snake(name: str) -> str:
30
+ """驼峰命名转蛇形命名。"""
31
+ s1 = re.sub(r"(.)([A-Z][a-z]+)", r"\1_\2", name)
32
+ return re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", s1).lower()
33
+
34
+
35
+ def snake_to_camel(name: str, *, upper_first: bool = False) -> str:
36
+ """蛇形命名转驼峰命名。"""
37
+ parts = name.split("_")
38
+ if not parts:
39
+ return name
40
+ head, *tail = parts
41
+ camel = head if upper_first else head.lower()
42
+ camel += "".join(word.capitalize() for word in tail)
43
+ return camel
44
+
45
+
46
+ def remove_whitespace(text: str) -> str:
47
+ """移除所有空白字符。"""
48
+ return re.sub(r"\s+", "", text)
49
+
50
+
51
+ def is_blank(text: str | None) -> bool:
52
+ """判断文本是否为空或仅含空白。"""
53
+ return text is None or not text.strip()