chatenv 0.2.0__tar.gz → 0.2.2__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.
- {chatenv-0.2.0/src/chatenv.egg-info → chatenv-0.2.2}/PKG-INFO +6 -3
- {chatenv-0.2.0 → chatenv-0.2.2}/README.md +3 -0
- {chatenv-0.2.0 → chatenv-0.2.2}/pyproject.toml +2 -2
- {chatenv-0.2.0 → chatenv-0.2.2}/src/chatenv/__init__.py +1 -1
- {chatenv-0.2.0 → chatenv-0.2.2}/src/chatenv/cli.py +75 -3
- {chatenv-0.2.0 → chatenv-0.2.2/src/chatenv.egg-info}/PKG-INFO +6 -3
- {chatenv-0.2.0 → chatenv-0.2.2}/src/chatenv.egg-info/requires.txt +2 -2
- {chatenv-0.2.0 → chatenv-0.2.2}/tests/test_chatenv_core.py +78 -1
- chatenv-0.2.2/tests/test_version.py +14 -0
- chatenv-0.2.0/tests/test_version.py +0 -5
- {chatenv-0.2.0 → chatenv-0.2.2}/LICENSE +0 -0
- {chatenv-0.2.0 → chatenv-0.2.2}/setup.cfg +0 -0
- {chatenv-0.2.0 → chatenv-0.2.2}/src/chatenv/configs.py +0 -0
- {chatenv-0.2.0 → chatenv-0.2.2}/src/chatenv/discovery.py +0 -0
- {chatenv-0.2.0 → chatenv-0.2.2}/src/chatenv/fields.py +0 -0
- {chatenv-0.2.0 → chatenv-0.2.2}/src/chatenv/paste.py +0 -0
- {chatenv-0.2.0 → chatenv-0.2.2}/src/chatenv/paths.py +0 -0
- {chatenv-0.2.0 → chatenv-0.2.2}/src/chatenv/presets/__init__.py +0 -0
- {chatenv-0.2.0 → chatenv-0.2.2}/src/chatenv/registry.py +0 -0
- {chatenv-0.2.0 → chatenv-0.2.2}/src/chatenv/source_chain.py +0 -0
- {chatenv-0.2.0 → chatenv-0.2.2}/src/chatenv/store.py +0 -0
- {chatenv-0.2.0 → chatenv-0.2.2}/src/chatenv/utils.py +0 -0
- {chatenv-0.2.0 → chatenv-0.2.2}/src/chatenv.egg-info/SOURCES.txt +0 -0
- {chatenv-0.2.0 → chatenv-0.2.2}/src/chatenv.egg-info/dependency_links.txt +0 -0
- {chatenv-0.2.0 → chatenv-0.2.2}/src/chatenv.egg-info/entry_points.txt +0 -0
- {chatenv-0.2.0 → chatenv-0.2.2}/src/chatenv.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chatenv
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: ChatArch typed environment profile manager
|
|
5
5
|
Author-email: rexwzh <1073853456@qq.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -11,8 +11,8 @@ Requires-Python: >=3.10
|
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
License-File: LICENSE
|
|
13
13
|
Requires-Dist: chatstyle<0.2.0,>=0.1.0
|
|
14
|
-
Requires-Dist: click
|
|
15
|
-
Requires-Dist: python-dotenv
|
|
14
|
+
Requires-Dist: click<9.0,>=8.4.2
|
|
15
|
+
Requires-Dist: python-dotenv<2.0,>=1.2.2
|
|
16
16
|
Provides-Extra: dev
|
|
17
17
|
Requires-Dist: build>=1.0; extra == "dev"
|
|
18
18
|
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
@@ -86,7 +86,10 @@ $CHATARCH_HOME/envs/
|
|
|
86
86
|
`chatenv` CLI 基于已注册 schema 工作。单独安装且没有项目注册 schema 时,它会提示需要先注册配置类型。业务项目通常会在自己的 CLI 中导入 schema 后复用这些能力。
|
|
87
87
|
|
|
88
88
|
```bash
|
|
89
|
+
chatenv --version
|
|
89
90
|
chatenv init -t example
|
|
91
|
+
chatenv status
|
|
92
|
+
chatenv status --detail
|
|
90
93
|
chatenv cat -t example
|
|
91
94
|
chatenv cat -t example --no-mask | chatenv paste --stdin --profile work --yes
|
|
92
95
|
chatenv set EXAMPLE_API_KEY=sk-xxx
|
|
@@ -59,7 +59,10 @@ $CHATARCH_HOME/envs/
|
|
|
59
59
|
`chatenv` CLI 基于已注册 schema 工作。单独安装且没有项目注册 schema 时,它会提示需要先注册配置类型。业务项目通常会在自己的 CLI 中导入 schema 后复用这些能力。
|
|
60
60
|
|
|
61
61
|
```bash
|
|
62
|
+
chatenv --version
|
|
62
63
|
chatenv init -t example
|
|
64
|
+
chatenv status
|
|
65
|
+
chatenv status --detail
|
|
63
66
|
chatenv cat -t example
|
|
64
67
|
chatenv cat -t example --no-mask | chatenv paste --stdin --profile work --yes
|
|
65
68
|
chatenv set EXAMPLE_API_KEY=sk-xxx
|
|
@@ -13,8 +13,8 @@ authors = [{name = "rexwzh", email = "1073853456@qq.com"}]
|
|
|
13
13
|
keywords = ["chatenv", "chatarch", "env"]
|
|
14
14
|
dependencies = [
|
|
15
15
|
"chatstyle>=0.1.0,<0.2.0",
|
|
16
|
-
"click>=8.0",
|
|
17
|
-
"python-dotenv>=1.0",
|
|
16
|
+
"click>=8.4.2,<9.0",
|
|
17
|
+
"python-dotenv>=1.2.2,<2.0",
|
|
18
18
|
]
|
|
19
19
|
classifiers = [
|
|
20
20
|
"Programming Language :: Python :: 3",
|
|
@@ -19,7 +19,8 @@ from chatstyle import (
|
|
|
19
19
|
resolve_interactive_mode as _chatstyle_resolve_interactive_mode,
|
|
20
20
|
)
|
|
21
21
|
|
|
22
|
-
from .
|
|
22
|
+
from . import __version__
|
|
23
|
+
from .discovery import get_provider_errors, load_config_providers
|
|
23
24
|
from .fields import BaseEnvConfig, EnvField, normalize_profile_name
|
|
24
25
|
from .paste import iter_fields_for_values, parse_pasted_env_text
|
|
25
26
|
from .paths import get_paths
|
|
@@ -104,6 +105,7 @@ COMMAND_ORDER = [
|
|
|
104
105
|
"paste",
|
|
105
106
|
"use",
|
|
106
107
|
"list",
|
|
108
|
+
"status",
|
|
107
109
|
"cat",
|
|
108
110
|
"get",
|
|
109
111
|
"set",
|
|
@@ -122,6 +124,7 @@ class OrderedGroup(click.Group):
|
|
|
122
124
|
|
|
123
125
|
|
|
124
126
|
@click.group(name="chatenv", cls=OrderedGroup)
|
|
127
|
+
@click.version_option(__version__, prog_name="chatenv")
|
|
125
128
|
@click.option("--home", type=click.Path(file_okay=False, path_type=Path), help="Override CHATARCH_HOME for this command.")
|
|
126
129
|
@click.pass_context
|
|
127
130
|
def cli(ctx: click.Context, home: Path | None):
|
|
@@ -187,6 +190,43 @@ def _available_config_types_message() -> str:
|
|
|
187
190
|
return "\n".join(lines)
|
|
188
191
|
|
|
189
192
|
|
|
193
|
+
def _provider_name(config_cls: type[BaseEnvConfig]) -> str:
|
|
194
|
+
return str(getattr(config_cls, "_provider", config_cls.__module__.split(".", 1)[0]))
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _status_config_line(config_cls: type[BaseEnvConfig], store: EnvStore) -> str:
|
|
198
|
+
aliases = ",".join(getattr(config_cls, "_aliases", [])) or "-"
|
|
199
|
+
profiles = store.list_profiles(config_cls)
|
|
200
|
+
active_marker = "yes" if store.active_path(config_cls).exists() else "no"
|
|
201
|
+
return (
|
|
202
|
+
f"- {config_cls.get_storage_name()} | title={config_cls._title} | "
|
|
203
|
+
f"provider={_provider_name(config_cls)} | aliases={aliases} | "
|
|
204
|
+
f"fields={len(config_cls.get_fields())} | active={active_marker} | profiles={len(profiles)}"
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _status_field_line(config_cls: type[BaseEnvConfig], field_name: str, field: EnvField) -> str:
|
|
209
|
+
value_set = field.value not in (None, "")
|
|
210
|
+
default = field.default
|
|
211
|
+
if field.is_sensitive and default not in (None, ""):
|
|
212
|
+
default_text = mask_secret(str(default))
|
|
213
|
+
else:
|
|
214
|
+
default_text = "" if default is None else str(default)
|
|
215
|
+
parts = [
|
|
216
|
+
f" - {field.env_key}",
|
|
217
|
+
f"name={field_name}",
|
|
218
|
+
f"provider={_provider_name(config_cls)}",
|
|
219
|
+
]
|
|
220
|
+
if field.desc:
|
|
221
|
+
parts.append(f"desc={field.desc}")
|
|
222
|
+
if default_text:
|
|
223
|
+
parts.append(f"default={default_text}")
|
|
224
|
+
if field.is_sensitive:
|
|
225
|
+
parts.append("sensitive=yes")
|
|
226
|
+
parts.append(f"configured={'yes' if value_set else 'no'}")
|
|
227
|
+
return " | ".join(parts)
|
|
228
|
+
|
|
229
|
+
|
|
190
230
|
def _select_config_interactive(
|
|
191
231
|
*,
|
|
192
232
|
action: str,
|
|
@@ -341,7 +381,7 @@ def init_env(ctx: click.Context, config_types: tuple[str, ...], interactive: boo
|
|
|
341
381
|
@click.option("--type", "config_types", "-t", multiple=True, help="Filter config types.")
|
|
342
382
|
@click.pass_context
|
|
343
383
|
def list_env(ctx: click.Context, config_types: tuple[str, ...]):
|
|
344
|
-
"""List
|
|
384
|
+
"""List active default and named profiles grouped by config type."""
|
|
345
385
|
store = _store(ctx)
|
|
346
386
|
configs = _matched_or_all(config_types)
|
|
347
387
|
_ensure_registered()
|
|
@@ -350,16 +390,48 @@ def list_env(ctx: click.Context, config_types: tuple[str, ...]):
|
|
|
350
390
|
found = False
|
|
351
391
|
for config_cls in configs:
|
|
352
392
|
profiles = store.list_profiles(config_cls)
|
|
353
|
-
|
|
393
|
+
has_default = store.active_path(config_cls).exists()
|
|
394
|
+
if not has_default and not profiles:
|
|
354
395
|
continue
|
|
355
396
|
found = True
|
|
356
397
|
click.echo(f"[{config_cls.get_storage_name()}]")
|
|
398
|
+
if has_default:
|
|
399
|
+
click.echo("- .env [default]")
|
|
357
400
|
for profile in profiles:
|
|
358
401
|
click.echo(f"- {profile}.env")
|
|
359
402
|
if not found:
|
|
360
403
|
click.echo(f"No profiles found under {_envs_dir(ctx)}")
|
|
361
404
|
|
|
362
405
|
|
|
406
|
+
@cli.command(name="status")
|
|
407
|
+
@click.option("--type", "config_types", "-t", multiple=True, help="Filter config types.")
|
|
408
|
+
@click.option("--detail", is_flag=True, help="Show each registered variable and its provider package.")
|
|
409
|
+
@click.pass_context
|
|
410
|
+
def status_env(ctx: click.Context, config_types: tuple[str, ...], detail: bool):
|
|
411
|
+
"""Show registered config platforms and provider ownership."""
|
|
412
|
+
store = _store(ctx)
|
|
413
|
+
configs = _matched_or_all(config_types)
|
|
414
|
+
_ensure_registered()
|
|
415
|
+
if config_types and not configs:
|
|
416
|
+
raise click.ClickException(f"No configuration types matched: {', '.join(config_types)}")
|
|
417
|
+
_load_all(ctx)
|
|
418
|
+
click.echo(f"Registered config platforms: {len(configs)}")
|
|
419
|
+
for index, config_cls in enumerate(configs):
|
|
420
|
+
if index:
|
|
421
|
+
click.echo("")
|
|
422
|
+
click.echo(_status_config_line(config_cls, store))
|
|
423
|
+
if not detail:
|
|
424
|
+
continue
|
|
425
|
+
for field_name, field in config_cls.get_fields().items():
|
|
426
|
+
click.echo(_status_field_line(config_cls, field_name, field))
|
|
427
|
+
errors = get_provider_errors()
|
|
428
|
+
if errors:
|
|
429
|
+
click.echo("")
|
|
430
|
+
click.echo(f"Provider load errors: {len(errors)}")
|
|
431
|
+
for provider, exc in errors.items():
|
|
432
|
+
click.echo(f"- {provider}: {exc}")
|
|
433
|
+
|
|
434
|
+
|
|
363
435
|
@cli.command(name="cat")
|
|
364
436
|
@click.argument("name", required=False)
|
|
365
437
|
@click.option("--no-mask", is_flag=True, help="Show sensitive values in plain text.")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chatenv
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: ChatArch typed environment profile manager
|
|
5
5
|
Author-email: rexwzh <1073853456@qq.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -11,8 +11,8 @@ Requires-Python: >=3.10
|
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
License-File: LICENSE
|
|
13
13
|
Requires-Dist: chatstyle<0.2.0,>=0.1.0
|
|
14
|
-
Requires-Dist: click
|
|
15
|
-
Requires-Dist: python-dotenv
|
|
14
|
+
Requires-Dist: click<9.0,>=8.4.2
|
|
15
|
+
Requires-Dist: python-dotenv<2.0,>=1.2.2
|
|
16
16
|
Provides-Extra: dev
|
|
17
17
|
Requires-Dist: build>=1.0; extra == "dev"
|
|
18
18
|
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
@@ -86,7 +86,10 @@ $CHATARCH_HOME/envs/
|
|
|
86
86
|
`chatenv` CLI 基于已注册 schema 工作。单独安装且没有项目注册 schema 时,它会提示需要先注册配置类型。业务项目通常会在自己的 CLI 中导入 schema 后复用这些能力。
|
|
87
87
|
|
|
88
88
|
```bash
|
|
89
|
+
chatenv --version
|
|
89
90
|
chatenv init -t example
|
|
91
|
+
chatenv status
|
|
92
|
+
chatenv status --detail
|
|
90
93
|
chatenv cat -t example
|
|
91
94
|
chatenv cat -t example --no-mask | chatenv paste --stdin --profile work --yes
|
|
92
95
|
chatenv set EXAMPLE_API_KEY=sk-xxx
|
|
@@ -92,13 +92,48 @@ def test_duplicate_provider_config_does_not_pollute_registry(monkeypatch):
|
|
|
92
92
|
discovery_module._provider_configs.update(provider_configs_before)
|
|
93
93
|
|
|
94
94
|
|
|
95
|
-
def
|
|
95
|
+
def test_runtime_dependencies_use_reviewed_latest_windows():
|
|
96
96
|
pyproject_text = (Path(__file__).resolve().parents[1] / "pyproject.toml").read_text(
|
|
97
97
|
encoding="utf-8"
|
|
98
98
|
)
|
|
99
99
|
|
|
100
100
|
assert '"chatstyle>=0.1.0,<0.2.0"' in pyproject_text
|
|
101
|
+
assert '"click>=8.4.2,<9.0"' in pyproject_text
|
|
102
|
+
assert '"python-dotenv>=1.2.2,<2.0"' in pyproject_text
|
|
101
103
|
assert '"chatstyle>=0.1.0"' not in pyproject_text
|
|
104
|
+
assert '"click>=8.0"' not in pyproject_text
|
|
105
|
+
assert '"python-dotenv>=1.0"' not in pyproject_text
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def test_status_lists_registered_platforms_and_provider_detail(tmp_path):
|
|
109
|
+
runner = CliRunner()
|
|
110
|
+
home = tmp_path / "arch"
|
|
111
|
+
result = runner.invoke(cli, ["--home", str(home), "status", "--type", "unit", "--detail"])
|
|
112
|
+
|
|
113
|
+
assert result.exit_code == 0, result.output
|
|
114
|
+
assert "Registered config platforms: 1" in result.output
|
|
115
|
+
assert "Unit | title=Unit Configuration" in result.output
|
|
116
|
+
assert "provider=" in result.output
|
|
117
|
+
assert "UNIT_KEY" in result.output
|
|
118
|
+
assert "sensitive=yes" in result.output
|
|
119
|
+
assert "UNIT_VALUE" in result.output
|
|
120
|
+
assert "default=default" in result.output
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def test_status_reports_provider_name_from_loaded_package(tmp_path):
|
|
124
|
+
previous = getattr(UnitConfig, "_provider", None)
|
|
125
|
+
setattr(UnitConfig, "_provider", "chatunit")
|
|
126
|
+
try:
|
|
127
|
+
result = CliRunner().invoke(cli, ["--home", str(tmp_path / "arch"), "status", "--type", "unit", "--detail"])
|
|
128
|
+
finally:
|
|
129
|
+
if previous is None:
|
|
130
|
+
delattr(UnitConfig, "_provider")
|
|
131
|
+
else:
|
|
132
|
+
setattr(UnitConfig, "_provider", previous)
|
|
133
|
+
|
|
134
|
+
assert result.exit_code == 0, result.output
|
|
135
|
+
assert "provider=chatunit" in result.output
|
|
136
|
+
assert "UNIT_KEY | name=UNIT_KEY | provider=chatunit" in result.output
|
|
102
137
|
|
|
103
138
|
|
|
104
139
|
def test_cli_set_only_writes_the_requested_key(monkeypatch, tmp_path):
|
|
@@ -144,6 +179,47 @@ def test_cli_paste_active_merges_file_values_without_system_env(monkeypatch, tmp
|
|
|
144
179
|
assert "OPENAI_API_KEY" not in env_text
|
|
145
180
|
|
|
146
181
|
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def test_list_marks_active_default_profile(tmp_path):
|
|
185
|
+
runner = CliRunner()
|
|
186
|
+
home = tmp_path / "arch"
|
|
187
|
+
store = EnvStore(home / "envs")
|
|
188
|
+
store.save_active(UnitConfig, {"UNIT_VALUE": "active"})
|
|
189
|
+
store.save_profile(UnitConfig, "work", {"UNIT_VALUE": "named"})
|
|
190
|
+
|
|
191
|
+
result = runner.invoke(cli, ["--home", str(home), "list", "--type", "unit"])
|
|
192
|
+
|
|
193
|
+
assert result.exit_code == 0, result.output
|
|
194
|
+
assert "[Unit]" in result.output
|
|
195
|
+
assert "- .env [default]" in result.output
|
|
196
|
+
assert "- work.env" in result.output
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def test_list_keeps_named_only_profiles_without_default_marker(tmp_path):
|
|
201
|
+
runner = CliRunner()
|
|
202
|
+
home = tmp_path / "arch"
|
|
203
|
+
store = EnvStore(home / "envs")
|
|
204
|
+
store.save_profile(UnitConfig, "work", {"UNIT_VALUE": "named"})
|
|
205
|
+
|
|
206
|
+
result = runner.invoke(cli, ["--home", str(home), "list", "--type", "unit"])
|
|
207
|
+
|
|
208
|
+
assert result.exit_code == 0, result.output
|
|
209
|
+
assert "[Unit]" in result.output
|
|
210
|
+
assert "- .env [default]" not in result.output
|
|
211
|
+
assert "- work.env" in result.output
|
|
212
|
+
|
|
213
|
+
def test_list_reports_no_profiles_when_active_and_named_profiles_absent(tmp_path):
|
|
214
|
+
runner = CliRunner()
|
|
215
|
+
home = tmp_path / "arch"
|
|
216
|
+
(home / "envs" / "Unit").mkdir(parents=True)
|
|
217
|
+
|
|
218
|
+
result = runner.invoke(cli, ["--home", str(home), "list", "--type", "unit"])
|
|
219
|
+
|
|
220
|
+
assert result.exit_code == 0, result.output
|
|
221
|
+
assert "No profiles found under" in result.output
|
|
222
|
+
|
|
147
223
|
def test_paths_only_use_chatarch_home(monkeypatch, tmp_path):
|
|
148
224
|
monkeypatch.setenv("CHATARCH_HOME", str(tmp_path / "arch"))
|
|
149
225
|
paths = get_paths()
|
|
@@ -548,6 +624,7 @@ def test_cli_help_uses_stable_command_order():
|
|
|
548
624
|
"paste",
|
|
549
625
|
"use",
|
|
550
626
|
"list",
|
|
627
|
+
"status",
|
|
551
628
|
"cat",
|
|
552
629
|
"get",
|
|
553
630
|
"set",
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from chatenv import __version__
|
|
2
|
+
from chatenv.cli import cli
|
|
3
|
+
from click.testing import CliRunner
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_version_present():
|
|
7
|
+
assert __version__ == "0.2.2"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_cli_version_option():
|
|
11
|
+
result = CliRunner().invoke(cli, ["--version"])
|
|
12
|
+
|
|
13
|
+
assert result.exit_code == 0
|
|
14
|
+
assert __version__ in result.output
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|