fakerx 0.2.0__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.
- fakerx-0.2.0/LICENSE +21 -0
- fakerx-0.2.0/PKG-INFO +209 -0
- fakerx-0.2.0/README.md +181 -0
- fakerx-0.2.0/fakerx/__init__.py +3 -0
- fakerx-0.2.0/fakerx/fakerx.py +302 -0
- fakerx-0.2.0/fakerx.egg-info/PKG-INFO +209 -0
- fakerx-0.2.0/fakerx.egg-info/SOURCES.txt +11 -0
- fakerx-0.2.0/fakerx.egg-info/dependency_links.txt +1 -0
- fakerx-0.2.0/fakerx.egg-info/entry_points.txt +2 -0
- fakerx-0.2.0/fakerx.egg-info/requires.txt +4 -0
- fakerx-0.2.0/fakerx.egg-info/top_level.txt +1 -0
- fakerx-0.2.0/pyproject.toml +45 -0
- fakerx-0.2.0/setup.cfg +4 -0
fakerx-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 影子
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
fakerx-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fakerx
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: 强大的Python数据生成库,Faker的增强版
|
|
5
|
+
Author-email: YingZi <yxdszlkc@163.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/yourusername/fakerx
|
|
8
|
+
Project-URL: Repository, https://github.com/yourusername/fakerx
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Requires-Python: >=3.8
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: faker
|
|
24
|
+
Requires-Dist: pydantic
|
|
25
|
+
Requires-Dist: pydantic[email]
|
|
26
|
+
Requires-Dist: pyyaml
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# FakerX
|
|
30
|
+
|
|
31
|
+
FakerX 是一个强大的 Python 库,用于生成高质量的测试数据。它是 Faker 库的增强版,提供了更丰富的自定义选项、更灵活的数据结构生成,并集成了数据验证功能。
|
|
32
|
+
|
|
33
|
+
## 安装
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install fakerx
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 基础用法
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from fakerx import FakerX
|
|
43
|
+
|
|
44
|
+
fake = FakerX('zh_CN')
|
|
45
|
+
|
|
46
|
+
name = fake.name()
|
|
47
|
+
address = fake.address()
|
|
48
|
+
date = fake.date_of_birth()
|
|
49
|
+
|
|
50
|
+
print(f'生成姓名: {name}')
|
|
51
|
+
print(f'生成地址: {address}')
|
|
52
|
+
print(f'生成出生日期: {date}')
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## 结构化数据生成
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
user_schema = {
|
|
59
|
+
'id': '{pyint}',
|
|
60
|
+
'username': '{user_name}',
|
|
61
|
+
'email': '{email}',
|
|
62
|
+
'profile': {
|
|
63
|
+
'level': {'elements': ['初级', '中级', '高级']}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
user_data = fake.schema(user_schema, iterations=2)
|
|
68
|
+
print(user_data)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## 数据验证
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from pydantic import BaseModel, EmailStr, conint
|
|
75
|
+
|
|
76
|
+
class User(BaseModel):
|
|
77
|
+
id: conint(gt=0)
|
|
78
|
+
name: str
|
|
79
|
+
email: EmailStr
|
|
80
|
+
|
|
81
|
+
valid_user = fake.pydantic(User)
|
|
82
|
+
print(valid_user)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## 批处理
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
usernames = fake.batch('user_name', iterations=1000, unique=True)
|
|
89
|
+
print(usernames[:5])
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## 唯一字段约束
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
user_schema = {
|
|
96
|
+
'id': '{pyint}',
|
|
97
|
+
'username': '{user_name}',
|
|
98
|
+
'email': '{email}'
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
user_data = fake.schema(user_schema, iterations=5, unique_fields=['username', 'email'])
|
|
102
|
+
print(user_data)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## 数据导出
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
# 导出为JSON
|
|
109
|
+
json_data = fake.to_json(user_data)
|
|
110
|
+
print(json_data)
|
|
111
|
+
|
|
112
|
+
# 导出为CSV
|
|
113
|
+
csv_data = fake.to_csv(user_data)
|
|
114
|
+
print(csv_data)
|
|
115
|
+
|
|
116
|
+
# 保存到文件
|
|
117
|
+
fake.to_csv(user_data, 'users.csv')
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## 高级功能
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
# 生成UUID
|
|
124
|
+
uid = fake.uuid4()
|
|
125
|
+
print(f'UUID: {uid}')
|
|
126
|
+
|
|
127
|
+
# 自定义URL
|
|
128
|
+
custom_url = fake.custom_url('example.com')
|
|
129
|
+
print(f'自定义URL: {custom_url}')
|
|
130
|
+
|
|
131
|
+
# 邮箱验证
|
|
132
|
+
is_valid = fake.validate_email('test@example.com')
|
|
133
|
+
print(f'邮箱有效: {is_valid}')
|
|
134
|
+
|
|
135
|
+
# 设置随机种子
|
|
136
|
+
fake.seed(42)
|
|
137
|
+
name1 = fake.name()
|
|
138
|
+
fake.seed(42)
|
|
139
|
+
name2 = fake.name()
|
|
140
|
+
print(f'重现结果: {name1 == name2}')
|
|
141
|
+
|
|
142
|
+
# 批量生成(生成器)
|
|
143
|
+
usernames_gen = fake.batch('user_name', iterations=1000, unique=True)
|
|
144
|
+
first_5 = list(usernames_gen)[:5]
|
|
145
|
+
print(f'前5个用户名: {first_5}')
|
|
146
|
+
|
|
147
|
+
# 数据统计
|
|
148
|
+
stats = fake.stats(user_data)
|
|
149
|
+
print(f'数据统计: {stats}')
|
|
150
|
+
|
|
151
|
+
# 带验证的数据生成
|
|
152
|
+
valid_email = fake.generate_with_validation('email', lambda x: 'test' not in x)
|
|
153
|
+
print(f'有效邮箱: {valid_email}')
|
|
154
|
+
```
|
|
155
|
+
## 命令行工具
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
# 安装后可以使用命令行工具
|
|
159
|
+
pip install -e .
|
|
160
|
+
|
|
161
|
+
# 生成10个用户名
|
|
162
|
+
fakerx --method user_name --count 10
|
|
163
|
+
|
|
164
|
+
# 从schema文件生成数据
|
|
165
|
+
fakerx --schema user_schema.json --count 5 --output users.json
|
|
166
|
+
|
|
167
|
+
# 生成CSV格式数据
|
|
168
|
+
fakerx --schema user_schema.json --count 5 --format csv --output users.csv
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## 数据库集成
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
# 将数据插入SQLite数据库
|
|
175
|
+
fake.to_database(user_data, 'users', 'test.db')
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## 配置文件支持
|
|
179
|
+
|
|
180
|
+
创建 `config.yaml`:
|
|
181
|
+
```yaml
|
|
182
|
+
schema:
|
|
183
|
+
id: '{pyint}'
|
|
184
|
+
name: '{name}'
|
|
185
|
+
email: '{email}'
|
|
186
|
+
iterations: 10
|
|
187
|
+
unique_fields: ['email']
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
# 从配置文件生成数据
|
|
192
|
+
data = fake.generate_from_config('config.yaml')
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## 异步生成
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
import asyncio
|
|
199
|
+
|
|
200
|
+
async def main():
|
|
201
|
+
# 异步批量生成
|
|
202
|
+
names = await fake.async_batch('name', iterations=100)
|
|
203
|
+
print(names[:5])
|
|
204
|
+
|
|
205
|
+
asyncio.run(main())
|
|
206
|
+
```
|
|
207
|
+
## 许可证
|
|
208
|
+
|
|
209
|
+
MIT
|
fakerx-0.2.0/README.md
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# FakerX
|
|
2
|
+
|
|
3
|
+
FakerX 是一个强大的 Python 库,用于生成高质量的测试数据。它是 Faker 库的增强版,提供了更丰富的自定义选项、更灵活的数据结构生成,并集成了数据验证功能。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install fakerx
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 基础用法
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from fakerx import FakerX
|
|
15
|
+
|
|
16
|
+
fake = FakerX('zh_CN')
|
|
17
|
+
|
|
18
|
+
name = fake.name()
|
|
19
|
+
address = fake.address()
|
|
20
|
+
date = fake.date_of_birth()
|
|
21
|
+
|
|
22
|
+
print(f'生成姓名: {name}')
|
|
23
|
+
print(f'生成地址: {address}')
|
|
24
|
+
print(f'生成出生日期: {date}')
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 结构化数据生成
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
user_schema = {
|
|
31
|
+
'id': '{pyint}',
|
|
32
|
+
'username': '{user_name}',
|
|
33
|
+
'email': '{email}',
|
|
34
|
+
'profile': {
|
|
35
|
+
'level': {'elements': ['初级', '中级', '高级']}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
user_data = fake.schema(user_schema, iterations=2)
|
|
40
|
+
print(user_data)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## 数据验证
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from pydantic import BaseModel, EmailStr, conint
|
|
47
|
+
|
|
48
|
+
class User(BaseModel):
|
|
49
|
+
id: conint(gt=0)
|
|
50
|
+
name: str
|
|
51
|
+
email: EmailStr
|
|
52
|
+
|
|
53
|
+
valid_user = fake.pydantic(User)
|
|
54
|
+
print(valid_user)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## 批处理
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
usernames = fake.batch('user_name', iterations=1000, unique=True)
|
|
61
|
+
print(usernames[:5])
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## 唯一字段约束
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
user_schema = {
|
|
68
|
+
'id': '{pyint}',
|
|
69
|
+
'username': '{user_name}',
|
|
70
|
+
'email': '{email}'
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
user_data = fake.schema(user_schema, iterations=5, unique_fields=['username', 'email'])
|
|
74
|
+
print(user_data)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## 数据导出
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
# 导出为JSON
|
|
81
|
+
json_data = fake.to_json(user_data)
|
|
82
|
+
print(json_data)
|
|
83
|
+
|
|
84
|
+
# 导出为CSV
|
|
85
|
+
csv_data = fake.to_csv(user_data)
|
|
86
|
+
print(csv_data)
|
|
87
|
+
|
|
88
|
+
# 保存到文件
|
|
89
|
+
fake.to_csv(user_data, 'users.csv')
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## 高级功能
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
# 生成UUID
|
|
96
|
+
uid = fake.uuid4()
|
|
97
|
+
print(f'UUID: {uid}')
|
|
98
|
+
|
|
99
|
+
# 自定义URL
|
|
100
|
+
custom_url = fake.custom_url('example.com')
|
|
101
|
+
print(f'自定义URL: {custom_url}')
|
|
102
|
+
|
|
103
|
+
# 邮箱验证
|
|
104
|
+
is_valid = fake.validate_email('test@example.com')
|
|
105
|
+
print(f'邮箱有效: {is_valid}')
|
|
106
|
+
|
|
107
|
+
# 设置随机种子
|
|
108
|
+
fake.seed(42)
|
|
109
|
+
name1 = fake.name()
|
|
110
|
+
fake.seed(42)
|
|
111
|
+
name2 = fake.name()
|
|
112
|
+
print(f'重现结果: {name1 == name2}')
|
|
113
|
+
|
|
114
|
+
# 批量生成(生成器)
|
|
115
|
+
usernames_gen = fake.batch('user_name', iterations=1000, unique=True)
|
|
116
|
+
first_5 = list(usernames_gen)[:5]
|
|
117
|
+
print(f'前5个用户名: {first_5}')
|
|
118
|
+
|
|
119
|
+
# 数据统计
|
|
120
|
+
stats = fake.stats(user_data)
|
|
121
|
+
print(f'数据统计: {stats}')
|
|
122
|
+
|
|
123
|
+
# 带验证的数据生成
|
|
124
|
+
valid_email = fake.generate_with_validation('email', lambda x: 'test' not in x)
|
|
125
|
+
print(f'有效邮箱: {valid_email}')
|
|
126
|
+
```
|
|
127
|
+
## 命令行工具
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
# 安装后可以使用命令行工具
|
|
131
|
+
pip install -e .
|
|
132
|
+
|
|
133
|
+
# 生成10个用户名
|
|
134
|
+
fakerx --method user_name --count 10
|
|
135
|
+
|
|
136
|
+
# 从schema文件生成数据
|
|
137
|
+
fakerx --schema user_schema.json --count 5 --output users.json
|
|
138
|
+
|
|
139
|
+
# 生成CSV格式数据
|
|
140
|
+
fakerx --schema user_schema.json --count 5 --format csv --output users.csv
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## 数据库集成
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
# 将数据插入SQLite数据库
|
|
147
|
+
fake.to_database(user_data, 'users', 'test.db')
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## 配置文件支持
|
|
151
|
+
|
|
152
|
+
创建 `config.yaml`:
|
|
153
|
+
```yaml
|
|
154
|
+
schema:
|
|
155
|
+
id: '{pyint}'
|
|
156
|
+
name: '{name}'
|
|
157
|
+
email: '{email}'
|
|
158
|
+
iterations: 10
|
|
159
|
+
unique_fields: ['email']
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
# 从配置文件生成数据
|
|
164
|
+
data = fake.generate_from_config('config.yaml')
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## 异步生成
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
import asyncio
|
|
171
|
+
|
|
172
|
+
async def main():
|
|
173
|
+
# 异步批量生成
|
|
174
|
+
names = await fake.async_batch('name', iterations=100)
|
|
175
|
+
print(names[:5])
|
|
176
|
+
|
|
177
|
+
asyncio.run(main())
|
|
178
|
+
```
|
|
179
|
+
## 许可证
|
|
180
|
+
|
|
181
|
+
MIT
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# -----------------------------
|
|
3
|
+
# @Author : 影子
|
|
4
|
+
# @Time : 2026/1/1 14:47
|
|
5
|
+
# @Software : PyCharm
|
|
6
|
+
# @FileName : fakerx.py
|
|
7
|
+
# -----------------------------
|
|
8
|
+
from faker import Faker
|
|
9
|
+
import re
|
|
10
|
+
import json
|
|
11
|
+
import csv
|
|
12
|
+
import uuid
|
|
13
|
+
import sqlite3
|
|
14
|
+
import yaml
|
|
15
|
+
import asyncio
|
|
16
|
+
from io import StringIO
|
|
17
|
+
from typing import Any, Dict, List, Union, Generator
|
|
18
|
+
from pydantic import BaseModel
|
|
19
|
+
from datetime import datetime, date
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class FakerX(Faker):
|
|
23
|
+
def __init__(self, locale='en_US', **kwargs):
|
|
24
|
+
super().__init__(locale, **kwargs)
|
|
25
|
+
|
|
26
|
+
def uuid4(self) -> str:
|
|
27
|
+
"""生成UUID4字符串"""
|
|
28
|
+
return str(uuid.uuid4())
|
|
29
|
+
|
|
30
|
+
def custom_url(self, domain: str = None) -> str:
|
|
31
|
+
"""生成自定义域名的URL"""
|
|
32
|
+
if domain:
|
|
33
|
+
return f"https://{domain}/{self.slug()}"
|
|
34
|
+
return self.url()
|
|
35
|
+
|
|
36
|
+
def validate_email(self, email: str) -> bool:
|
|
37
|
+
"""验证邮箱格式"""
|
|
38
|
+
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
|
|
39
|
+
return bool(re.match(pattern, email))
|
|
40
|
+
|
|
41
|
+
def set_seed(self, seed_value: int) -> None:
|
|
42
|
+
"""设置随机种子以便重现结果"""
|
|
43
|
+
Faker.seed(seed_value)
|
|
44
|
+
|
|
45
|
+
def _resolve_template(self, template: str) -> str:
|
|
46
|
+
def replacer(match):
|
|
47
|
+
method = match.group(1)
|
|
48
|
+
if hasattr(self, method):
|
|
49
|
+
return str(getattr(self, method)())
|
|
50
|
+
else:
|
|
51
|
+
return match.group(0)
|
|
52
|
+
|
|
53
|
+
return re.sub(r'\{(\w+)\}', replacer, template)
|
|
54
|
+
|
|
55
|
+
def _generate_from_schema(self, schema: Union[Dict, List, str, Any]) -> Any:
|
|
56
|
+
if isinstance(schema, str):
|
|
57
|
+
return self._resolve_template(schema)
|
|
58
|
+
elif isinstance(schema, list):
|
|
59
|
+
return [self._generate_from_schema(item) for item in schema]
|
|
60
|
+
elif isinstance(schema, dict):
|
|
61
|
+
if 'elements' in schema and len(schema) == 1:
|
|
62
|
+
# Special case for random_element
|
|
63
|
+
return self.random_element(schema['elements'])
|
|
64
|
+
else:
|
|
65
|
+
result = {}
|
|
66
|
+
for key, value in schema.items():
|
|
67
|
+
result[key] = self._generate_from_schema(value)
|
|
68
|
+
return result
|
|
69
|
+
else:
|
|
70
|
+
return schema
|
|
71
|
+
|
|
72
|
+
def schema(self, schema_dict: Dict, iterations: int = 1, unique_fields: List[str] = None) -> List[Dict]:
|
|
73
|
+
results = []
|
|
74
|
+
unique_values = {field: set() for field in (unique_fields or [])}
|
|
75
|
+
for _ in range(iterations):
|
|
76
|
+
result = {}
|
|
77
|
+
for key, value in schema_dict.items():
|
|
78
|
+
if isinstance(value, str) and value.startswith('{') and value.endswith('}'):
|
|
79
|
+
# 处理模板字符串
|
|
80
|
+
method_name = value[1:-1]
|
|
81
|
+
if hasattr(self, method_name):
|
|
82
|
+
val = getattr(self, method_name)()
|
|
83
|
+
if key in unique_values:
|
|
84
|
+
while val in unique_values[key]:
|
|
85
|
+
val = getattr(self, method_name)()
|
|
86
|
+
unique_values[key].add(val)
|
|
87
|
+
result[key] = val
|
|
88
|
+
else:
|
|
89
|
+
result[key] = value
|
|
90
|
+
elif callable(value):
|
|
91
|
+
# 直接调用可调用对象
|
|
92
|
+
result[key] = value()
|
|
93
|
+
else:
|
|
94
|
+
# 递归处理嵌套结构
|
|
95
|
+
if isinstance(value, (dict, list)):
|
|
96
|
+
result[key] = self._generate_from_schema(value)
|
|
97
|
+
else:
|
|
98
|
+
result[key] = value
|
|
99
|
+
results.append(result)
|
|
100
|
+
return results
|
|
101
|
+
|
|
102
|
+
def to_json(self, data: List[Dict]) -> str:
|
|
103
|
+
"""将数据列表转换为JSON字符串"""
|
|
104
|
+
return json.dumps(data, ensure_ascii=False, indent=2)
|
|
105
|
+
|
|
106
|
+
def to_csv(self, data: List[Dict], filename: str = None) -> str:
|
|
107
|
+
"""将数据列表转换为CSV字符串或保存到文件"""
|
|
108
|
+
if not data:
|
|
109
|
+
return ""
|
|
110
|
+
output = StringIO()
|
|
111
|
+
writer = csv.DictWriter(output, fieldnames=data[0].keys())
|
|
112
|
+
writer.writeheader()
|
|
113
|
+
writer.writerows(data)
|
|
114
|
+
csv_string = output.getvalue()
|
|
115
|
+
if filename:
|
|
116
|
+
with open(filename, 'w', newline='', encoding='utf-8') as f:
|
|
117
|
+
f.write(csv_string)
|
|
118
|
+
return f"Data saved to {filename}"
|
|
119
|
+
return csv_string
|
|
120
|
+
|
|
121
|
+
def stats(self, data: List[Dict]) -> Dict[str, Any]:
|
|
122
|
+
"""统计生成数据的统计信息"""
|
|
123
|
+
if not data:
|
|
124
|
+
return {}
|
|
125
|
+
|
|
126
|
+
stats = {}
|
|
127
|
+
for key in data[0].keys():
|
|
128
|
+
values = [item[key] for item in data]
|
|
129
|
+
if isinstance(values[0], (int, float)):
|
|
130
|
+
stats[key] = {
|
|
131
|
+
'count': len(values),
|
|
132
|
+
'min': min(values),
|
|
133
|
+
'max': max(values),
|
|
134
|
+
'avg': sum(values) / len(values)
|
|
135
|
+
}
|
|
136
|
+
elif isinstance(values[0], str):
|
|
137
|
+
stats[key] = {
|
|
138
|
+
'count': len(values),
|
|
139
|
+
'unique': len(set(values)),
|
|
140
|
+
'avg_length': sum(len(v) for v in values) / len(values)
|
|
141
|
+
}
|
|
142
|
+
else:
|
|
143
|
+
stats[key] = {'count': len(values)}
|
|
144
|
+
return stats
|
|
145
|
+
|
|
146
|
+
def to_database(self, data: List[Dict], table_name: str, db_path: str = ':memory:',
|
|
147
|
+
if_exists: str = 'replace') -> None:
|
|
148
|
+
"""将数据插入到SQLite数据库"""
|
|
149
|
+
conn = sqlite3.connect(db_path)
|
|
150
|
+
try:
|
|
151
|
+
if data:
|
|
152
|
+
# 创建表
|
|
153
|
+
columns = list(data[0].keys())
|
|
154
|
+
column_defs = ', '.join([f'{col} TEXT' for col in columns])
|
|
155
|
+
create_sql = f'CREATE TABLE {table_name} ({column_defs})'
|
|
156
|
+
|
|
157
|
+
if if_exists == 'replace':
|
|
158
|
+
conn.execute(f'DROP TABLE IF EXISTS {table_name}')
|
|
159
|
+
|
|
160
|
+
conn.execute(create_sql)
|
|
161
|
+
|
|
162
|
+
# 插入数据
|
|
163
|
+
placeholders = ', '.join(['?' for _ in columns])
|
|
164
|
+
insert_sql = f'INSERT INTO {table_name} VALUES ({placeholders})'
|
|
165
|
+
|
|
166
|
+
for row in data:
|
|
167
|
+
values = [str(row[col]) for col in columns]
|
|
168
|
+
conn.execute(insert_sql, values)
|
|
169
|
+
|
|
170
|
+
conn.commit()
|
|
171
|
+
print(f"成功插入 {len(data)} 条记录到表 {table_name}")
|
|
172
|
+
finally:
|
|
173
|
+
conn.close()
|
|
174
|
+
|
|
175
|
+
def load_schema_from_file(self, file_path: str) -> Dict:
|
|
176
|
+
"""从JSON或YAML文件加载schema"""
|
|
177
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
178
|
+
if file_path.endswith('.yaml') or file_path.endswith('.yml'):
|
|
179
|
+
return yaml.safe_load(f)
|
|
180
|
+
else:
|
|
181
|
+
return json.load(f)
|
|
182
|
+
|
|
183
|
+
def generate_from_config(self, config_path: str) -> List[Dict]:
|
|
184
|
+
"""从配置文件生成数据"""
|
|
185
|
+
config = self.load_schema_from_file(config_path)
|
|
186
|
+
schema = config.get('schema', {})
|
|
187
|
+
iterations = config.get('iterations', 1)
|
|
188
|
+
unique_fields = config.get('unique_fields', [])
|
|
189
|
+
return self.schema(schema, iterations, unique_fields)
|
|
190
|
+
|
|
191
|
+
async def async_batch(self, method: str, iterations: int = 1, unique: bool = False) -> List[Any]:
|
|
192
|
+
"""异步批量生成数据"""
|
|
193
|
+
loop = asyncio.get_event_loop()
|
|
194
|
+
results = []
|
|
195
|
+
seen = set() if unique else None
|
|
196
|
+
|
|
197
|
+
for _ in range(iterations):
|
|
198
|
+
# 在线程池中运行同步方法
|
|
199
|
+
value = await loop.run_in_executor(None, getattr(self, method))
|
|
200
|
+
if unique:
|
|
201
|
+
while value in seen:
|
|
202
|
+
value = await loop.run_in_executor(None, getattr(self, method))
|
|
203
|
+
seen.add(value)
|
|
204
|
+
results.append(value)
|
|
205
|
+
|
|
206
|
+
return results
|
|
207
|
+
|
|
208
|
+
def nested_schema(self, schema_dict: Dict, iterations: int = 1, unique_fields: List[str] = None) -> List[Dict]:
|
|
209
|
+
"""生成嵌套结构的schema数据,支持更复杂的嵌套"""
|
|
210
|
+
return self.schema(schema_dict, iterations, unique_fields)
|
|
211
|
+
|
|
212
|
+
def generate_with_validation(self, method: str, validator: callable = None, max_retries: int = 10) -> Any:
|
|
213
|
+
"""生成数据并使用自定义验证器验证"""
|
|
214
|
+
for _ in range(max_retries):
|
|
215
|
+
value = getattr(self, method)()
|
|
216
|
+
if validator is None or validator(value):
|
|
217
|
+
return value
|
|
218
|
+
raise ValueError(f"Failed to generate valid value for {method} after {max_retries} attempts")
|
|
219
|
+
|
|
220
|
+
def pydantic(self, model: BaseModel, max_retries: int = 100) -> BaseModel:
|
|
221
|
+
# Check Pydantic version
|
|
222
|
+
try:
|
|
223
|
+
# Pydantic v2
|
|
224
|
+
fields = model.model_fields
|
|
225
|
+
|
|
226
|
+
def get_type(field_info):
|
|
227
|
+
return field_info.annotation
|
|
228
|
+
except AttributeError:
|
|
229
|
+
# Pydantic v1
|
|
230
|
+
fields = model.__fields__
|
|
231
|
+
|
|
232
|
+
def get_type(field_info):
|
|
233
|
+
return field_info.type_
|
|
234
|
+
|
|
235
|
+
for attempt in range(max_retries):
|
|
236
|
+
data = {}
|
|
237
|
+
for field_name, field_info in fields.items():
|
|
238
|
+
method_name = field_name
|
|
239
|
+
if hasattr(self, method_name):
|
|
240
|
+
data[field_name] = getattr(self, method_name)()
|
|
241
|
+
else:
|
|
242
|
+
# Fallback to random data based on type
|
|
243
|
+
field_type = get_type(field_info)
|
|
244
|
+
if hasattr(field_type, '__origin__') and field_type.__origin__ is Union:
|
|
245
|
+
# Handle Union/Optional types
|
|
246
|
+
args = field_type.__args__
|
|
247
|
+
non_none_types = [t for t in args if t is not type(None)]
|
|
248
|
+
if non_none_types:
|
|
249
|
+
field_type = non_none_types[0]
|
|
250
|
+
|
|
251
|
+
if field_type == int:
|
|
252
|
+
data[field_name] = self.pyint()
|
|
253
|
+
elif field_type == str:
|
|
254
|
+
# Check for specific string field names like email
|
|
255
|
+
if 'email' in field_name.lower():
|
|
256
|
+
data[field_name] = self.email()
|
|
257
|
+
else:
|
|
258
|
+
data[field_name] = self.text(max_nb_chars=20)
|
|
259
|
+
elif field_type == float:
|
|
260
|
+
data[field_name] = self.pyfloat()
|
|
261
|
+
elif field_type == bool:
|
|
262
|
+
data[field_name] = self.pybool()
|
|
263
|
+
elif field_type == datetime:
|
|
264
|
+
data[field_name] = self.date_time()
|
|
265
|
+
elif field_type == date:
|
|
266
|
+
data[field_name] = self.date_object()
|
|
267
|
+
else:
|
|
268
|
+
data[field_name] = self.text(max_nb_chars=20)
|
|
269
|
+
|
|
270
|
+
try:
|
|
271
|
+
return model(**data)
|
|
272
|
+
except Exception as e:
|
|
273
|
+
if attempt == max_retries - 1:
|
|
274
|
+
raise RuntimeError(f"Failed to create valid {model.__name__} after {max_retries} attempts") from e
|
|
275
|
+
continue
|
|
276
|
+
|
|
277
|
+
def batch(self, method: str, iterations: int = 1, unique: bool = False) -> Generator[Any, None, None]:
|
|
278
|
+
if not hasattr(self, method):
|
|
279
|
+
raise AttributeError(f"No such method: {method}")
|
|
280
|
+
seen = set() if unique else None
|
|
281
|
+
for _ in range(iterations):
|
|
282
|
+
value = getattr(self, method)()
|
|
283
|
+
if unique:
|
|
284
|
+
while value in seen:
|
|
285
|
+
value = getattr(self, method)()
|
|
286
|
+
seen.add(value)
|
|
287
|
+
yield value
|
|
288
|
+
|
|
289
|
+
def clean_address(self) -> str:
|
|
290
|
+
"""生成不带邮编的地址"""
|
|
291
|
+
full_address = self.address()
|
|
292
|
+
# 使用正则表达式移除末尾的邮编(通常是6位数字)
|
|
293
|
+
cleaned = re.sub(r'\s*\d{6}\s*$', '', full_address).strip()
|
|
294
|
+
return cleaned
|
|
295
|
+
|
|
296
|
+
def random_date_between(self, start_date: str = '2000-01-01', end_date: str = '2023-12-31') -> str:
|
|
297
|
+
"""生成指定日期范围内的随机日期"""
|
|
298
|
+
from datetime import datetime
|
|
299
|
+
start = datetime.strptime(start_date, '%Y-%m-%d')
|
|
300
|
+
end = datetime.strptime(end_date, '%Y-%m-%d')
|
|
301
|
+
random_date = self.date_time_between(start_date=start, end_date=end)
|
|
302
|
+
return random_date.strftime('%Y-%m-%d')
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fakerx
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: 强大的Python数据生成库,Faker的增强版
|
|
5
|
+
Author-email: YingZi <yxdszlkc@163.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/yourusername/fakerx
|
|
8
|
+
Project-URL: Repository, https://github.com/yourusername/fakerx
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Requires-Python: >=3.8
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: faker
|
|
24
|
+
Requires-Dist: pydantic
|
|
25
|
+
Requires-Dist: pydantic[email]
|
|
26
|
+
Requires-Dist: pyyaml
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# FakerX
|
|
30
|
+
|
|
31
|
+
FakerX 是一个强大的 Python 库,用于生成高质量的测试数据。它是 Faker 库的增强版,提供了更丰富的自定义选项、更灵活的数据结构生成,并集成了数据验证功能。
|
|
32
|
+
|
|
33
|
+
## 安装
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install fakerx
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 基础用法
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from fakerx import FakerX
|
|
43
|
+
|
|
44
|
+
fake = FakerX('zh_CN')
|
|
45
|
+
|
|
46
|
+
name = fake.name()
|
|
47
|
+
address = fake.address()
|
|
48
|
+
date = fake.date_of_birth()
|
|
49
|
+
|
|
50
|
+
print(f'生成姓名: {name}')
|
|
51
|
+
print(f'生成地址: {address}')
|
|
52
|
+
print(f'生成出生日期: {date}')
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## 结构化数据生成
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
user_schema = {
|
|
59
|
+
'id': '{pyint}',
|
|
60
|
+
'username': '{user_name}',
|
|
61
|
+
'email': '{email}',
|
|
62
|
+
'profile': {
|
|
63
|
+
'level': {'elements': ['初级', '中级', '高级']}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
user_data = fake.schema(user_schema, iterations=2)
|
|
68
|
+
print(user_data)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## 数据验证
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from pydantic import BaseModel, EmailStr, conint
|
|
75
|
+
|
|
76
|
+
class User(BaseModel):
|
|
77
|
+
id: conint(gt=0)
|
|
78
|
+
name: str
|
|
79
|
+
email: EmailStr
|
|
80
|
+
|
|
81
|
+
valid_user = fake.pydantic(User)
|
|
82
|
+
print(valid_user)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## 批处理
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
usernames = fake.batch('user_name', iterations=1000, unique=True)
|
|
89
|
+
print(usernames[:5])
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## 唯一字段约束
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
user_schema = {
|
|
96
|
+
'id': '{pyint}',
|
|
97
|
+
'username': '{user_name}',
|
|
98
|
+
'email': '{email}'
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
user_data = fake.schema(user_schema, iterations=5, unique_fields=['username', 'email'])
|
|
102
|
+
print(user_data)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## 数据导出
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
# 导出为JSON
|
|
109
|
+
json_data = fake.to_json(user_data)
|
|
110
|
+
print(json_data)
|
|
111
|
+
|
|
112
|
+
# 导出为CSV
|
|
113
|
+
csv_data = fake.to_csv(user_data)
|
|
114
|
+
print(csv_data)
|
|
115
|
+
|
|
116
|
+
# 保存到文件
|
|
117
|
+
fake.to_csv(user_data, 'users.csv')
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## 高级功能
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
# 生成UUID
|
|
124
|
+
uid = fake.uuid4()
|
|
125
|
+
print(f'UUID: {uid}')
|
|
126
|
+
|
|
127
|
+
# 自定义URL
|
|
128
|
+
custom_url = fake.custom_url('example.com')
|
|
129
|
+
print(f'自定义URL: {custom_url}')
|
|
130
|
+
|
|
131
|
+
# 邮箱验证
|
|
132
|
+
is_valid = fake.validate_email('test@example.com')
|
|
133
|
+
print(f'邮箱有效: {is_valid}')
|
|
134
|
+
|
|
135
|
+
# 设置随机种子
|
|
136
|
+
fake.seed(42)
|
|
137
|
+
name1 = fake.name()
|
|
138
|
+
fake.seed(42)
|
|
139
|
+
name2 = fake.name()
|
|
140
|
+
print(f'重现结果: {name1 == name2}')
|
|
141
|
+
|
|
142
|
+
# 批量生成(生成器)
|
|
143
|
+
usernames_gen = fake.batch('user_name', iterations=1000, unique=True)
|
|
144
|
+
first_5 = list(usernames_gen)[:5]
|
|
145
|
+
print(f'前5个用户名: {first_5}')
|
|
146
|
+
|
|
147
|
+
# 数据统计
|
|
148
|
+
stats = fake.stats(user_data)
|
|
149
|
+
print(f'数据统计: {stats}')
|
|
150
|
+
|
|
151
|
+
# 带验证的数据生成
|
|
152
|
+
valid_email = fake.generate_with_validation('email', lambda x: 'test' not in x)
|
|
153
|
+
print(f'有效邮箱: {valid_email}')
|
|
154
|
+
```
|
|
155
|
+
## 命令行工具
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
# 安装后可以使用命令行工具
|
|
159
|
+
pip install -e .
|
|
160
|
+
|
|
161
|
+
# 生成10个用户名
|
|
162
|
+
fakerx --method user_name --count 10
|
|
163
|
+
|
|
164
|
+
# 从schema文件生成数据
|
|
165
|
+
fakerx --schema user_schema.json --count 5 --output users.json
|
|
166
|
+
|
|
167
|
+
# 生成CSV格式数据
|
|
168
|
+
fakerx --schema user_schema.json --count 5 --format csv --output users.csv
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## 数据库集成
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
# 将数据插入SQLite数据库
|
|
175
|
+
fake.to_database(user_data, 'users', 'test.db')
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## 配置文件支持
|
|
179
|
+
|
|
180
|
+
创建 `config.yaml`:
|
|
181
|
+
```yaml
|
|
182
|
+
schema:
|
|
183
|
+
id: '{pyint}'
|
|
184
|
+
name: '{name}'
|
|
185
|
+
email: '{email}'
|
|
186
|
+
iterations: 10
|
|
187
|
+
unique_fields: ['email']
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
# 从配置文件生成数据
|
|
192
|
+
data = fake.generate_from_config('config.yaml')
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## 异步生成
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
import asyncio
|
|
199
|
+
|
|
200
|
+
async def main():
|
|
201
|
+
# 异步批量生成
|
|
202
|
+
names = await fake.async_batch('name', iterations=100)
|
|
203
|
+
print(names[:5])
|
|
204
|
+
|
|
205
|
+
asyncio.run(main())
|
|
206
|
+
```
|
|
207
|
+
## 许可证
|
|
208
|
+
|
|
209
|
+
MIT
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
fakerx/__init__.py
|
|
5
|
+
fakerx/fakerx.py
|
|
6
|
+
fakerx.egg-info/PKG-INFO
|
|
7
|
+
fakerx.egg-info/SOURCES.txt
|
|
8
|
+
fakerx.egg-info/dependency_links.txt
|
|
9
|
+
fakerx.egg-info/entry_points.txt
|
|
10
|
+
fakerx.egg-info/requires.txt
|
|
11
|
+
fakerx.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
fakerx
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
# 构建系统要求
|
|
3
|
+
requires = ["setuptools>=61", "wheel"]
|
|
4
|
+
build-backend = "setuptools.build_meta"
|
|
5
|
+
|
|
6
|
+
[project]
|
|
7
|
+
# 基础元数据
|
|
8
|
+
name = "fakerx"
|
|
9
|
+
version = "0.2.0"
|
|
10
|
+
authors = [
|
|
11
|
+
{ name = "YingZi", email = "yxdszlkc@163.com" }
|
|
12
|
+
]
|
|
13
|
+
description = "强大的Python数据生成库,Faker的增强版"
|
|
14
|
+
readme = "README.md"
|
|
15
|
+
requires-python = ">=3.8"
|
|
16
|
+
license = { text = "MIT" }
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 4 - Beta",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"License :: OSI Approved :: MIT License",
|
|
21
|
+
"Operating System :: OS Independent",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3.8",
|
|
24
|
+
"Programming Language :: Python :: 3.9",
|
|
25
|
+
"Programming Language :: Python :: 3.10",
|
|
26
|
+
"Programming Language :: Python :: 3.11",
|
|
27
|
+
"Programming Language :: Python :: 3.12",
|
|
28
|
+
"Programming Language :: Python :: 3.13",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
dependencies = [
|
|
32
|
+
"faker",
|
|
33
|
+
"pydantic",
|
|
34
|
+
"pydantic[email]",
|
|
35
|
+
"pyyaml"
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
# 项目 URL
|
|
39
|
+
[project.urls]
|
|
40
|
+
Homepage = "https://github.com/yourusername/fakerx"
|
|
41
|
+
Repository = "https://github.com/yourusername/fakerx"
|
|
42
|
+
|
|
43
|
+
# 控制台脚本入口
|
|
44
|
+
[project.scripts]
|
|
45
|
+
fakerx = "fakerx_cli:main"
|
fakerx-0.2.0/setup.cfg
ADDED