chameleon-data 0.1.1__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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 hidariutiwa
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.
@@ -0,0 +1,117 @@
1
+ Metadata-Version: 2.4
2
+ Name: chameleon-data
3
+ Version: 0.1.1
4
+ Summary: Interactive CLI to mask columns of CSV/TSV/XLSX files with Faker-generated fake data
5
+ Keywords: cli,faker,masking,anonymize,csv,excel
6
+ Author: hidariutiwa
7
+ Author-email: hidariutiwa <hidariutiwa22@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Environment :: Console
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Utilities
14
+ Requires-Dist: faker>=40.43.0
15
+ Requires-Dist: openpyxl>=3.1.5
16
+ Requires-Dist: pandas>=3.1.0
17
+ Requires-Dist: pydantic>=2.14.1
18
+ Requires-Dist: questionary>=2.1.1
19
+ Requires-Dist: rich>=15.0.0
20
+ Requires-Python: >=3.11
21
+ Project-URL: Repository, https://github.com/hidariutiwa/chameleon-data
22
+ Description-Content-Type: text/markdown
23
+
24
+ # chameleon-data
25
+
26
+ CSV / TSV / XLSX ファイルの列を選んで、[Faker](https://faker.readthedocs.io/) で生成したダミーデータに置き換える対話型 CLI です。結果は元と同じ形式で保存されます。
27
+
28
+ ## インストール
29
+
30
+ Python 3.11 以上が必要です。
31
+
32
+ ```bash
33
+ # CLI ツールとしてインストール (おすすめ)
34
+ uv tool install chameleon-data
35
+
36
+ # インストールせずに 1 回だけ実行
37
+ uvx --from chameleon-data chameleon -f data.csv
38
+
39
+ # 他プロジェクトの依存として追加
40
+ uv add chameleon-data
41
+ pip install chameleon-data
42
+ ```
43
+
44
+ 最新の開発版は GitHub から直接インストールできます。
45
+
46
+ ```bash
47
+ uv tool install git+https://github.com/hidariutiwa/chameleon-data
48
+ ```
49
+
50
+ GitHub 版で特定のバージョンに固定したい場合は `git+https://github.com/hidariutiwa/chameleon-data@<tag or commit>` のように指定します。
51
+
52
+ アップデート / アンインストール:
53
+
54
+ ```bash
55
+ uv tool upgrade chameleon-data
56
+ uv tool uninstall chameleon-data
57
+ ```
58
+
59
+ ## 使い方
60
+
61
+ ```bash
62
+ chameleon -f data.csv # data_masked.csv に保存
63
+ chameleon -f data.xlsx -o out.xlsx # 出力先を指定
64
+ chameleon -f data.tsv -l en_US -s 42 # ロケールとシードを指定
65
+ ```
66
+
67
+ | オプション | 説明 |
68
+ | --- | --- |
69
+ | `-f`, `--filepath` | 入力ファイル (必須)。`.csv` / `.tsv` / `.xlsx` |
70
+ | `-o`, `--output` | 出力ファイル。デフォルトは `<ファイル名>_masked.<拡張子>` |
71
+ | `-l`, `--language` | Faker のロケール。デフォルトは `ja_JP` |
72
+ | `-s`, `--seed` | 乱数シード。指定すると毎回同じ結果になります |
73
+ | `-v`, `--version` | バージョンを表示 |
74
+
75
+ 実行すると、マスクする列をチェックボックスで選び、列ごとに置き換え方を選択します。
76
+
77
+ | 種類 | 内容 |
78
+ | --- | --- |
79
+ | `masking` | すべての文字を `*` に置き換え (文字数は維持) |
80
+ | `full_name` / `first_name` / `last_name` | 氏名 |
81
+ | `full_address` | 住所 |
82
+ | `zipcode` | 郵便番号 |
83
+ | `phone_number` | 電話番号 |
84
+
85
+ 同じ値は同じダミー値に置き換わるため、行同士の対応関係は保たれます。空セルは空のままです。
86
+
87
+ ### サンプルデータ
88
+
89
+ ```bash
90
+ chameleon-example # ./example/example.csv にサンプルを生成
91
+ chameleon -f example/example.csv
92
+ ```
93
+
94
+ ## 開発
95
+
96
+ [uv](https://docs.astral.sh/uv/) を使います。
97
+
98
+ ```bash
99
+ uv sync
100
+ uv run chameleon -f example/example.csv
101
+ uv build # dist/ に wheel と sdist を作成
102
+ ```
103
+
104
+ ### リリース
105
+
106
+ `v*` タグを push すると GitHub Actions (`.github/workflows/publish.yml`) が PyPI に公開します (Trusted Publishing)。
107
+
108
+ ```bash
109
+ uv version --bump patch # pyproject.toml の version を更新 (minor / major も可)
110
+ git commit -am "chore: release v$(uv version --short)"
111
+ git tag "v$(uv version --short)"
112
+ git push origin main --tags
113
+ ```
114
+
115
+ ## ライセンス
116
+
117
+ MIT
@@ -0,0 +1,94 @@
1
+ # chameleon-data
2
+
3
+ CSV / TSV / XLSX ファイルの列を選んで、[Faker](https://faker.readthedocs.io/) で生成したダミーデータに置き換える対話型 CLI です。結果は元と同じ形式で保存されます。
4
+
5
+ ## インストール
6
+
7
+ Python 3.11 以上が必要です。
8
+
9
+ ```bash
10
+ # CLI ツールとしてインストール (おすすめ)
11
+ uv tool install chameleon-data
12
+
13
+ # インストールせずに 1 回だけ実行
14
+ uvx --from chameleon-data chameleon -f data.csv
15
+
16
+ # 他プロジェクトの依存として追加
17
+ uv add chameleon-data
18
+ pip install chameleon-data
19
+ ```
20
+
21
+ 最新の開発版は GitHub から直接インストールできます。
22
+
23
+ ```bash
24
+ uv tool install git+https://github.com/hidariutiwa/chameleon-data
25
+ ```
26
+
27
+ GitHub 版で特定のバージョンに固定したい場合は `git+https://github.com/hidariutiwa/chameleon-data@<tag or commit>` のように指定します。
28
+
29
+ アップデート / アンインストール:
30
+
31
+ ```bash
32
+ uv tool upgrade chameleon-data
33
+ uv tool uninstall chameleon-data
34
+ ```
35
+
36
+ ## 使い方
37
+
38
+ ```bash
39
+ chameleon -f data.csv # data_masked.csv に保存
40
+ chameleon -f data.xlsx -o out.xlsx # 出力先を指定
41
+ chameleon -f data.tsv -l en_US -s 42 # ロケールとシードを指定
42
+ ```
43
+
44
+ | オプション | 説明 |
45
+ | --- | --- |
46
+ | `-f`, `--filepath` | 入力ファイル (必須)。`.csv` / `.tsv` / `.xlsx` |
47
+ | `-o`, `--output` | 出力ファイル。デフォルトは `<ファイル名>_masked.<拡張子>` |
48
+ | `-l`, `--language` | Faker のロケール。デフォルトは `ja_JP` |
49
+ | `-s`, `--seed` | 乱数シード。指定すると毎回同じ結果になります |
50
+ | `-v`, `--version` | バージョンを表示 |
51
+
52
+ 実行すると、マスクする列をチェックボックスで選び、列ごとに置き換え方を選択します。
53
+
54
+ | 種類 | 内容 |
55
+ | --- | --- |
56
+ | `masking` | すべての文字を `*` に置き換え (文字数は維持) |
57
+ | `full_name` / `first_name` / `last_name` | 氏名 |
58
+ | `full_address` | 住所 |
59
+ | `zipcode` | 郵便番号 |
60
+ | `phone_number` | 電話番号 |
61
+
62
+ 同じ値は同じダミー値に置き換わるため、行同士の対応関係は保たれます。空セルは空のままです。
63
+
64
+ ### サンプルデータ
65
+
66
+ ```bash
67
+ chameleon-example # ./example/example.csv にサンプルを生成
68
+ chameleon -f example/example.csv
69
+ ```
70
+
71
+ ## 開発
72
+
73
+ [uv](https://docs.astral.sh/uv/) を使います。
74
+
75
+ ```bash
76
+ uv sync
77
+ uv run chameleon -f example/example.csv
78
+ uv build # dist/ に wheel と sdist を作成
79
+ ```
80
+
81
+ ### リリース
82
+
83
+ `v*` タグを push すると GitHub Actions (`.github/workflows/publish.yml`) が PyPI に公開します (Trusted Publishing)。
84
+
85
+ ```bash
86
+ uv version --bump patch # pyproject.toml の version を更新 (minor / major も可)
87
+ git commit -am "chore: release v$(uv version --short)"
88
+ git tag "v$(uv version --short)"
89
+ git push origin main --tags
90
+ ```
91
+
92
+ ## ライセンス
93
+
94
+ MIT
@@ -0,0 +1,45 @@
1
+ [project]
2
+ name = "chameleon-data"
3
+ version = "0.1.1"
4
+ description = "Interactive CLI to mask columns of CSV/TSV/XLSX files with Faker-generated fake data"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ license = "MIT"
8
+ license-files = ["LICENSE"]
9
+ keywords = [
10
+ "cli",
11
+ "faker",
12
+ "masking",
13
+ "anonymize",
14
+ "csv",
15
+ "excel",
16
+ ]
17
+ classifiers = [
18
+ "Environment :: Console",
19
+ "Operating System :: OS Independent",
20
+ "Programming Language :: Python :: 3",
21
+ "Topic :: Utilities",
22
+ ]
23
+ dependencies = [
24
+ "faker>=40.43.0",
25
+ "openpyxl>=3.1.5",
26
+ "pandas>=3.1.0",
27
+ "pydantic>=2.14.1",
28
+ "questionary>=2.1.1",
29
+ "rich>=15.0.0",
30
+ ]
31
+
32
+ [[project.authors]]
33
+ name = "hidariutiwa"
34
+ email = "hidariutiwa22@gmail.com"
35
+
36
+ [project.urls]
37
+ Repository = "https://github.com/hidariutiwa/chameleon-data"
38
+
39
+ [project.scripts]
40
+ chameleon = "chameleon_data.main:main"
41
+ chameleon-example = "chameleon_data.main:create_example"
42
+
43
+ [build-system]
44
+ requires = ["uv_build>=0.11.14,<0.12.0"]
45
+ build-backend = "uv_build"
@@ -0,0 +1,37 @@
1
+ [project]
2
+ name = "chameleon-data"
3
+ version = "0.1.1"
4
+ description = "Interactive CLI to mask columns of CSV/TSV/XLSX files with Faker-generated fake data"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "hidariutiwa", email = "hidariutiwa22@gmail.com" }
8
+ ]
9
+ requires-python = ">=3.11"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ keywords = ["cli", "faker", "masking", "anonymize", "csv", "excel"]
13
+ classifiers = [
14
+ "Environment :: Console",
15
+ "Operating System :: OS Independent",
16
+ "Programming Language :: Python :: 3",
17
+ "Topic :: Utilities",
18
+ ]
19
+ dependencies = [
20
+ "faker>=40.43.0",
21
+ "openpyxl>=3.1.5",
22
+ "pandas>=3.1.0",
23
+ "pydantic>=2.14.1",
24
+ "questionary>=2.1.1",
25
+ "rich>=15.0.0",
26
+ ]
27
+
28
+ [project.urls]
29
+ Repository = "https://github.com/hidariutiwa/chameleon-data"
30
+
31
+ [build-system]
32
+ requires = ["uv_build>=0.11.14,<0.12.0"]
33
+ build-backend = "uv_build"
34
+
35
+ [project.scripts]
36
+ chameleon = "chameleon_data.main:main"
37
+ chameleon-example = "chameleon_data.main:create_example"
@@ -0,0 +1,2 @@
1
+ def hello() -> str:
2
+ return "Hello from chameleon-data!"
@@ -0,0 +1,190 @@
1
+ import sys
2
+ from argparse import ArgumentParser
3
+ from collections.abc import Callable
4
+ from importlib.metadata import version
5
+ from pathlib import Path
6
+
7
+ import pandas as pd
8
+ import questionary
9
+ from faker import Faker
10
+ from rich.console import Console
11
+ from pydantic import BaseModel
12
+
13
+
14
+ class ExampleData(BaseModel):
15
+ no: int
16
+ first_name: str
17
+ last_name: str
18
+ zipcode: str
19
+ address: str
20
+ phone_number: str
21
+
22
+
23
+ ALLOWED_SUFFIXES = (".csv", ".tsv", ".xlsx")
24
+
25
+ console = Console()
26
+
27
+
28
+ def fake_address(fake: Faker) -> str:
29
+ # prefecture, chome, etc. exist only in the ja_JP provider
30
+ if not hasattr(fake, "prefecture"):
31
+ return fake.address().replace("\n", " ")
32
+ return f"{fake.prefecture()}{fake.city()}{fake.chome()}{fake.gou()}{fake.ban()}{fake.building_name()}{fake.building_number()}"
33
+
34
+
35
+ FAKE_GENERATORS: dict[str, Callable[[Faker], str]] = {
36
+ "full_name": lambda fake: f"{fake.last_name()} {fake.first_name()}",
37
+ "first_name": lambda fake: fake.first_name(),
38
+ "last_name": lambda fake: fake.last_name(),
39
+ "full_address": fake_address,
40
+ "zipcode": lambda fake: fake.zipcode(),
41
+ "phone_number": lambda fake: fake.phone_number(),
42
+ }
43
+
44
+ FAKE_TYPES = ["masking", *FAKE_GENERATORS]
45
+
46
+
47
+ def read_table(path: Path) -> pd.DataFrame:
48
+ match path.suffix:
49
+ case ".csv":
50
+ return pd.read_csv(path, dtype=str)
51
+ case ".tsv":
52
+ return pd.read_csv(path, sep="\t", dtype=str)
53
+ case ".xlsx":
54
+ return pd.read_excel(path, dtype=str)
55
+ raise ValueError(f"{path.suffix} is not allowed.")
56
+
57
+
58
+ def write_table(df: pd.DataFrame, path: Path) -> None:
59
+ match path.suffix:
60
+ case ".csv":
61
+ df.to_csv(path, index=False)
62
+ case ".tsv":
63
+ df.to_csv(path, sep="\t", index=False)
64
+ case ".xlsx":
65
+ df.to_excel(path, index=False)
66
+ case _:
67
+ raise ValueError(f"{path.suffix} is not allowed.")
68
+
69
+
70
+ def mask_column(series: pd.Series, fake_type: str, fake: Faker) -> pd.Series:
71
+ # same original value -> same replacement, so relations between rows are kept
72
+ replaced: dict[str, str] = {}
73
+
74
+ def replace(value: str) -> str:
75
+ if pd.isna(value):
76
+ return value
77
+ if value not in replaced:
78
+ if fake_type == "masking":
79
+ replaced[value] = "*" * len(value)
80
+ else:
81
+ replaced[value] = FAKE_GENERATORS[fake_type](fake)
82
+ return replaced[value]
83
+
84
+ return series.map(replace)
85
+
86
+
87
+ def exit_with_error(message: str) -> None:
88
+ console.print(message, style="red bold")
89
+ sys.exit(1)
90
+
91
+
92
+ def main() -> None:
93
+ parser = ArgumentParser(
94
+ prog="chameleon",
95
+ description="mask columns of csv/tsv/xlsx file with fake data.",
96
+ )
97
+ parser.add_argument(
98
+ "-v", "--version", action="version", version=version("chameleon-data")
99
+ )
100
+ parser.add_argument(
101
+ "-f",
102
+ "--filepath",
103
+ required=True,
104
+ help="filepath read file. allowed format: csv, tsv, xlsx",
105
+ )
106
+ parser.add_argument(
107
+ "-o",
108
+ "--output",
109
+ help="filepath to save masked file. default: <filename>_masked.<suffix>",
110
+ )
111
+ parser.add_argument(
112
+ "-l",
113
+ "--language",
114
+ default="ja_JP",
115
+ help="language of fake data. default: ja_JP",
116
+ )
117
+ parser.add_argument(
118
+ "-s",
119
+ "--seed",
120
+ type=int,
121
+ help="you need same data everytimes, set seed any number.",
122
+ )
123
+ args = parser.parse_args()
124
+
125
+ input_path = Path(args.filepath).resolve()
126
+ if args.output:
127
+ output_path = Path(args.output).resolve()
128
+ else:
129
+ output_path = input_path.with_stem(f"{input_path.stem}_masked")
130
+
131
+ for path in (input_path, output_path):
132
+ if path.suffix not in ALLOWED_SUFFIXES:
133
+ exit_with_error(f"{path.suffix} is not allowed.")
134
+ if not input_path.exists():
135
+ exit_with_error(f"{input_path} is not found.")
136
+
137
+ df = read_table(input_path)
138
+
139
+ headers = list(df.columns)
140
+ mask_columns = questionary.checkbox("select masking columns", headers).ask()
141
+ if mask_columns is None:
142
+ sys.exit(1)
143
+ if not mask_columns:
144
+ exit_with_error("no columns selected.")
145
+
146
+ fake_types = {}
147
+ for column in mask_columns:
148
+ fake_type = questionary.select(f"type of {column}", FAKE_TYPES).ask()
149
+ if fake_type is None:
150
+ sys.exit(1)
151
+ fake_types[column] = fake_type
152
+
153
+ fake = Faker(args.language)
154
+ if args.seed is not None:
155
+ Faker.seed(args.seed)
156
+
157
+ for column, fake_type in fake_types.items():
158
+ df[column] = mask_column(df[column], fake_type, fake)
159
+
160
+ write_table(df, output_path)
161
+ console.print(f"save masked file: {output_path}", style="cyan bold")
162
+
163
+
164
+ def create_example() -> None:
165
+ Faker.seed(223)
166
+ fake = Faker("ja_JP")
167
+
168
+ persons = [
169
+ ExampleData(
170
+ no=i + 1,
171
+ first_name=fake.first_name(),
172
+ last_name=fake.last_name(),
173
+ zipcode=fake.zipcode(),
174
+ address=fake_address(fake),
175
+ phone_number=fake.phone_number(),
176
+ )
177
+ for i in range(10)
178
+ ]
179
+
180
+ df = pd.DataFrame([p.model_dump() for p in persons])
181
+ df = df.set_index("no")
182
+ print(df)
183
+
184
+ example_dir = Path(Path.cwd(), "example")
185
+ Path.mkdir(example_dir, exist_ok=True)
186
+ example_path = Path(example_dir, "example.csv")
187
+
188
+ df.to_csv(example_path)
189
+ console.print(f"save example csv: {example_path}", style="cyan bold")
190
+ console.print(f"try it! -> chameleon -f {example_path}", style="cyan bold")
File without changes