chatenv 0.2.1__tar.gz → 0.2.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.
Files changed (25) hide show
  1. {chatenv-0.2.1/src/chatenv.egg-info → chatenv-0.2.3}/PKG-INFO +3 -3
  2. {chatenv-0.2.1 → chatenv-0.2.3}/pyproject.toml +2 -2
  3. {chatenv-0.2.1 → chatenv-0.2.3}/src/chatenv/__init__.py +1 -1
  4. {chatenv-0.2.1 → chatenv-0.2.3}/src/chatenv/cli.py +27 -3
  5. {chatenv-0.2.1 → chatenv-0.2.3/src/chatenv.egg-info}/PKG-INFO +3 -3
  6. {chatenv-0.2.1 → chatenv-0.2.3}/src/chatenv.egg-info/requires.txt +2 -2
  7. {chatenv-0.2.1 → chatenv-0.2.3}/tests/test_chatenv_core.py +79 -1
  8. {chatenv-0.2.1 → chatenv-0.2.3}/tests/test_version.py +1 -1
  9. {chatenv-0.2.1 → chatenv-0.2.3}/LICENSE +0 -0
  10. {chatenv-0.2.1 → chatenv-0.2.3}/README.md +0 -0
  11. {chatenv-0.2.1 → chatenv-0.2.3}/setup.cfg +0 -0
  12. {chatenv-0.2.1 → chatenv-0.2.3}/src/chatenv/configs.py +0 -0
  13. {chatenv-0.2.1 → chatenv-0.2.3}/src/chatenv/discovery.py +0 -0
  14. {chatenv-0.2.1 → chatenv-0.2.3}/src/chatenv/fields.py +0 -0
  15. {chatenv-0.2.1 → chatenv-0.2.3}/src/chatenv/paste.py +0 -0
  16. {chatenv-0.2.1 → chatenv-0.2.3}/src/chatenv/paths.py +0 -0
  17. {chatenv-0.2.1 → chatenv-0.2.3}/src/chatenv/presets/__init__.py +0 -0
  18. {chatenv-0.2.1 → chatenv-0.2.3}/src/chatenv/registry.py +0 -0
  19. {chatenv-0.2.1 → chatenv-0.2.3}/src/chatenv/source_chain.py +0 -0
  20. {chatenv-0.2.1 → chatenv-0.2.3}/src/chatenv/store.py +0 -0
  21. {chatenv-0.2.1 → chatenv-0.2.3}/src/chatenv/utils.py +0 -0
  22. {chatenv-0.2.1 → chatenv-0.2.3}/src/chatenv.egg-info/SOURCES.txt +0 -0
  23. {chatenv-0.2.1 → chatenv-0.2.3}/src/chatenv.egg-info/dependency_links.txt +0 -0
  24. {chatenv-0.2.1 → chatenv-0.2.3}/src/chatenv.egg-info/entry_points.txt +0 -0
  25. {chatenv-0.2.1 → chatenv-0.2.3}/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.1
3
+ Version: 0.2.3
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>=8.0
15
- Requires-Dist: python-dotenv>=1.0
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"
@@ -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",
@@ -20,4 +20,4 @@ __all__ = [
20
20
  "__version__",
21
21
  ]
22
22
 
23
- __version__ = "0.2.1"
23
+ __version__ = "0.2.3"
@@ -277,7 +277,9 @@ def _resolve_config_types_or_prompt(
277
277
  return [_select_config_interactive(action=action, configs=selectable)]
278
278
 
279
279
  if not config_types and allow_multi:
280
- return list(BaseEnvConfig._registry)
280
+ raise click.ClickException(
281
+ f"{action} requires --type/-t outside interactive mode.\n{_available_config_types_message()}"
282
+ )
281
283
  if not config_types:
282
284
  raise click.ClickException(
283
285
  f"{action} requires --type/-t outside interactive mode.\n{_available_config_types_message()}"
@@ -377,11 +379,28 @@ def init_env(ctx: click.Context, config_types: tuple[str, ...], interactive: boo
377
379
  click.echo(f"Configuration saved to {_envs_dir(ctx)}")
378
380
 
379
381
 
382
+ def _has_configured_values(config_cls: type[BaseEnvConfig], values: dict[str, str]) -> bool:
383
+ """Return true when a profile has values beyond blanks and schema defaults."""
384
+ fields_by_key = {field.env_key: field for field in config_cls.get_fields().values()}
385
+ for key, value in values.items():
386
+ field = fields_by_key.get(key)
387
+ if field is None:
388
+ if value:
389
+ return True
390
+ continue
391
+ if value == "":
392
+ continue
393
+ if field.default is not None and str(field.default) == str(value):
394
+ continue
395
+ return True
396
+ return False
397
+
398
+
380
399
  @cli.command(name="list")
381
400
  @click.option("--type", "config_types", "-t", multiple=True, help="Filter config types.")
382
401
  @click.pass_context
383
402
  def list_env(ctx: click.Context, config_types: tuple[str, ...]):
384
- """List available named profiles grouped by config type."""
403
+ """List active default and named profiles grouped by config type."""
385
404
  store = _store(ctx)
386
405
  configs = _matched_or_all(config_types)
387
406
  _ensure_registered()
@@ -390,10 +409,15 @@ def list_env(ctx: click.Context, config_types: tuple[str, ...]):
390
409
  found = False
391
410
  for config_cls in configs:
392
411
  profiles = store.list_profiles(config_cls)
393
- if not profiles:
412
+ active_path = store.active_path(config_cls)
413
+ has_default = active_path.exists()
414
+ show_default = has_default and _has_configured_values(config_cls, store.load_active(config_cls))
415
+ if not show_default and not profiles:
394
416
  continue
395
417
  found = True
396
418
  click.echo(f"[{config_cls.get_storage_name()}]")
419
+ if show_default:
420
+ click.echo("- .env [default]")
397
421
  for profile in profiles:
398
422
  click.echo(f"- {profile}.env")
399
423
  if not found:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chatenv
3
- Version: 0.2.1
3
+ Version: 0.2.3
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>=8.0
15
- Requires-Dist: python-dotenv>=1.0
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"
@@ -1,6 +1,6 @@
1
1
  chatstyle<0.2.0,>=0.1.0
2
- click>=8.0
3
- python-dotenv>=1.0
2
+ click<9.0,>=8.4.2
3
+ python-dotenv<2.0,>=1.2.2
4
4
 
5
5
  [dev]
6
6
  build>=1.0
@@ -92,13 +92,17 @@ 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 test_chatarch_internal_dependencies_have_upper_bounds():
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
102
106
 
103
107
 
104
108
  def test_status_lists_registered_platforms_and_provider_detail(tmp_path):
@@ -175,6 +179,62 @@ def test_cli_paste_active_merges_file_values_without_system_env(monkeypatch, tmp
175
179
  assert "OPENAI_API_KEY" not in env_text
176
180
 
177
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
+
214
+ def test_list_hides_default_only_active_profile_by_default(tmp_path):
215
+ runner = CliRunner()
216
+ home = tmp_path / "arch"
217
+ store = EnvStore(home / "envs")
218
+ store.save_active(UnitConfig, {"UNIT_KEY": "", "UNIT_VALUE": "default"})
219
+
220
+ result = runner.invoke(cli, ["--home", str(home), "list", "--type", "unit"])
221
+
222
+ assert result.exit_code == 0, result.output
223
+ assert "No profiles found under" in result.output
224
+ assert "[Unit]" not in result.output
225
+ assert "- .env [default]" not in result.output
226
+
227
+
228
+ def test_list_reports_no_profiles_when_active_and_named_profiles_absent(tmp_path):
229
+ runner = CliRunner()
230
+ home = tmp_path / "arch"
231
+ (home / "envs" / "Unit").mkdir(parents=True)
232
+
233
+ result = runner.invoke(cli, ["--home", str(home), "list", "--type", "unit"])
234
+
235
+ assert result.exit_code == 0, result.output
236
+ assert "No profiles found under" in result.output
237
+
178
238
  def test_paths_only_use_chatarch_home(monkeypatch, tmp_path):
179
239
  monkeypatch.setenv("CHATARCH_HOME", str(tmp_path / "arch"))
180
240
  paths = get_paths()
@@ -485,6 +545,24 @@ def test_cli_init_with_type_still_prompts_fields_when_auto_prompt_disabled(
485
545
  assert "UNIT_VALUE='from-init'" in env_text
486
546
 
487
547
 
548
+ def test_cli_init_without_type_fails_outside_interactive_without_writing_all(
549
+ tmp_path, monkeypatch
550
+ ):
551
+ runner = CliRunner()
552
+ home = tmp_path / "arch"
553
+ monkeypatch.setenv("CHATARCH_AUTO_PROMPT", "false")
554
+ monkeypatch.setattr(
555
+ "chatenv.cli._chatstyle_resolve_interactive_mode",
556
+ _tty_resolution,
557
+ )
558
+
559
+ result = runner.invoke(cli, ["--home", str(home), "init", "-I"])
560
+
561
+ assert result.exit_code != 0
562
+ assert "init requires --type/-t outside interactive mode" in result.output
563
+ assert not (home / "envs").exists()
564
+
565
+
488
566
  def test_cli_new_prompts_for_config_type_when_type_missing(
489
567
  tmp_path, monkeypatch
490
568
  ):
@@ -4,7 +4,7 @@ from click.testing import CliRunner
4
4
 
5
5
 
6
6
  def test_version_present():
7
- assert __version__ == "0.2.1"
7
+ assert __version__ == "0.2.3"
8
8
 
9
9
 
10
10
  def test_cli_version_option():
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