ruyi 0.42.0b20251014__tar.gz → 0.42.0b20251015__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 (101) hide show
  1. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/PKG-INFO +2 -2
  2. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/pyproject.toml +2 -2
  3. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/cli/oobe.py +7 -1
  4. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/cli/self_cli.py +7 -2
  5. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/config/__init__.py +1 -7
  6. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/config/errors.py +10 -6
  7. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/config/schema.py +67 -17
  8. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/ruyipkg/state.py +10 -0
  9. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/utils/global_mode.py +19 -1
  10. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/LICENSE-Apache.txt +0 -0
  11. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/README.md +0 -0
  12. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/__init__.py +0 -0
  13. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/__main__.py +0 -0
  14. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/cli/__init__.py +0 -0
  15. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/cli/builtin_commands.py +0 -0
  16. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/cli/cmd.py +0 -0
  17. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/cli/completer.py +0 -0
  18. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/cli/completion.py +0 -0
  19. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/cli/config_cli.py +0 -0
  20. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/cli/main.py +0 -0
  21. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/cli/user_input.py +0 -0
  22. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/cli/version_cli.py +0 -0
  23. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/config/editor.py +0 -0
  24. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/config/news.py +0 -0
  25. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/device/__init__.py +0 -0
  26. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/device/provision.py +0 -0
  27. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/device/provision_cli.py +0 -0
  28. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/log/__init__.py +0 -0
  29. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/mux/__init__.py +0 -0
  30. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/mux/runtime.py +0 -0
  31. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/mux/venv/__init__.py +0 -0
  32. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/mux/venv/emulator_cfg.py +0 -0
  33. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/mux/venv/maker.py +0 -0
  34. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/mux/venv/venv_cli.py +0 -0
  35. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/mux/venv_cfg.py +0 -0
  36. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/pluginhost/__init__.py +0 -0
  37. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/pluginhost/api.py +0 -0
  38. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/pluginhost/ctx.py +0 -0
  39. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/pluginhost/paths.py +0 -0
  40. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/pluginhost/plugin_cli.py +0 -0
  41. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/pluginhost/unsandboxed.py +0 -0
  42. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/py.typed +0 -0
  43. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/resource_bundle/__init__.py +0 -0
  44. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/resource_bundle/__main__.py +0 -0
  45. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/resource_bundle/data.py +0 -0
  46. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/ruyipkg/__init__.py +0 -0
  47. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/ruyipkg/admin_checksum.py +0 -0
  48. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/ruyipkg/admin_cli.py +0 -0
  49. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/ruyipkg/atom.py +0 -0
  50. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/ruyipkg/augmented_pkg.py +0 -0
  51. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/ruyipkg/canonical_dump.py +0 -0
  52. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/ruyipkg/checksum.py +0 -0
  53. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/ruyipkg/cli_completion.py +0 -0
  54. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/ruyipkg/distfile.py +0 -0
  55. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/ruyipkg/entity.py +0 -0
  56. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/ruyipkg/entity_cli.py +0 -0
  57. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/ruyipkg/entity_provider.py +0 -0
  58. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/ruyipkg/fetcher.py +0 -0
  59. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/ruyipkg/host.py +0 -0
  60. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/ruyipkg/install.py +0 -0
  61. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/ruyipkg/install_cli.py +0 -0
  62. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/ruyipkg/list.py +0 -0
  63. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/ruyipkg/list_cli.py +0 -0
  64. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/ruyipkg/list_filter.py +0 -0
  65. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/ruyipkg/msg.py +0 -0
  66. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/ruyipkg/news.py +0 -0
  67. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/ruyipkg/news_cli.py +0 -0
  68. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/ruyipkg/news_store.py +0 -0
  69. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/ruyipkg/pkg_manifest.py +0 -0
  70. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/ruyipkg/profile.py +0 -0
  71. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/ruyipkg/profile_cli.py +0 -0
  72. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/ruyipkg/protocols.py +0 -0
  73. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/ruyipkg/repo.py +0 -0
  74. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/ruyipkg/unpack.py +0 -0
  75. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/ruyipkg/unpack_method.py +0 -0
  76. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/ruyipkg/update_cli.py +0 -0
  77. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/telemetry/__init__.py +0 -0
  78. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/telemetry/aggregate.py +0 -0
  79. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/telemetry/event.py +0 -0
  80. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/telemetry/node_info.py +0 -0
  81. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/telemetry/provider.py +0 -0
  82. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/telemetry/scope.py +0 -0
  83. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/telemetry/store.py +0 -0
  84. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/telemetry/telemetry_cli.py +0 -0
  85. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/utils/__init__.py +0 -0
  86. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/utils/ar.py +0 -0
  87. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/utils/ci.py +0 -0
  88. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/utils/frontmatter.py +0 -0
  89. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/utils/git.py +0 -0
  90. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/utils/l10n.py +0 -0
  91. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/utils/markdown.py +0 -0
  92. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/utils/mounts.py +0 -0
  93. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/utils/nuitka.py +0 -0
  94. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/utils/porcelain.py +0 -0
  95. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/utils/prereqs.py +0 -0
  96. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/utils/ssl_patch.py +0 -0
  97. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/utils/templating.py +0 -0
  98. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/utils/toml.py +0 -0
  99. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/utils/url.py +0 -0
  100. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/utils/xdg_basedir.py +0 -0
  101. {ruyi-0.42.0b20251014 → ruyi-0.42.0b20251015}/ruyi/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ruyi
3
- Version: 0.42.0b20251014
3
+ Version: 0.42.0b20251015
4
4
  Summary: Package manager for RuyiSDK
5
5
  License: Apache License
6
6
  Version 2.0, January 2004
@@ -221,7 +221,7 @@ Classifier: Topic :: Software Development :: Build Tools
221
221
  Classifier: Topic :: Software Development :: Embedded Systems
222
222
  Classifier: Topic :: System :: Software Distribution
223
223
  Classifier: Typing :: Typed
224
- Requires-Dist: argcomplete (>=2.0.0,<4.0.0)
224
+ Requires-Dist: argcomplete (>=2.0.0)
225
225
  Requires-Dist: arpy
226
226
  Requires-Dist: fastjsonschema (>=2.15.1)
227
227
  Requires-Dist: jinja2 (>=3,<4)
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [project]
6
6
  name = "ruyi"
7
- version = "0.42.0-beta.20251014"
7
+ version = "0.42.0-beta.20251015"
8
8
  description = "Package manager for RuyiSDK"
9
9
  keywords = ["ruyi", "ruyisdk"]
10
10
  license = { file = "LICENSE-Apache.txt" }
@@ -29,6 +29,7 @@ classifiers = [
29
29
  ]
30
30
  requires-python = ">=3.10"
31
31
  dependencies = [
32
+ "argcomplete>=2.0.0",
32
33
  "arpy",
33
34
  "fastjsonschema>=2.15.1",
34
35
  "jinja2 (>=3, <4)",
@@ -41,7 +42,6 @@ dependencies = [
41
42
  "tomlkit>=0.9",
42
43
  "tomli>=1.2; python_version<'3.11'",
43
44
  "tzdata; sys_platform=='win32'",
44
- "argcomplete (>=2.0.0,<4.0.0)",
45
45
  ]
46
46
 
47
47
  [project.scripts]
@@ -41,10 +41,16 @@ class OOBE:
41
41
  def should_prompt(self) -> bool:
42
42
  from ..utils.global_mode import is_env_var_truthy
43
43
 
44
+ if not sys.stdin.isatty() or not sys.stdout.isatty():
45
+ # This is of higher priority than even the debug override, because
46
+ # we don't want to mess up non-interactive sessions even in case of
47
+ # debugging.
48
+ return False
49
+
44
50
  if is_env_var_truthy(os.environ, "RUYI_DEBUG_FORCE_FIRST_RUN"):
45
51
  return True
46
52
 
47
- return self.is_first_run() and sys.stdin.isatty()
53
+ return self.is_first_run()
48
54
 
49
55
  def maybe_prompt(self) -> None:
50
56
  if not self.should_prompt():
@@ -131,12 +131,16 @@ class SelfCleanCommand(
131
131
  _do_reset(
132
132
  cfg,
133
133
  quiet=quiet,
134
+ # state-related
135
+ all_state=all,
136
+ news_read_status=news_read_status,
137
+ telemetry=telemetry,
138
+ # cache-related
139
+ all_cache=all,
134
140
  distfiles=distfiles,
135
141
  installed_pkgs=installed_pkgs,
136
- news_read_status=news_read_status,
137
142
  progcache=progcache,
138
143
  repo=repo,
139
- telemetry=telemetry,
140
144
  )
141
145
 
142
146
  return 0
@@ -233,6 +237,7 @@ def _do_reset(
233
237
  if installed_pkgs:
234
238
  status("removing installed packages")
235
239
  shutil.rmtree(cfg.data_root, True)
240
+ cfg.ruyipkg_global_state.purge_installation_info()
236
241
 
237
242
  # do not record any telemetry data if we're purging it
238
243
  if all_state or telemetry:
@@ -166,13 +166,7 @@ class GlobalConfig:
166
166
  attr_name = self._get_attr_name_by_key(section, sel)
167
167
  if attr_name is None:
168
168
  raise errors.InvalidConfigKeyError(key)
169
-
170
- expected_type = schema.get_expected_type_for_config_key(key)
171
- if not isinstance(value, expected_type):
172
- raise TypeError(
173
- f"expected type {expected_type.__name__} for config key '{key}', got {type(value).__name__}"
174
- )
175
-
169
+ schema.ensure_valid_config_kv(key, True, value)
176
170
  setattr(self, attr_name, value)
177
171
 
178
172
  @classmethod
@@ -31,7 +31,7 @@ class InvalidConfigValueTypeError(TypeError):
31
31
  self,
32
32
  key: str | Sequence[str],
33
33
  val: object | None,
34
- expected: type,
34
+ expected: type | Sequence[type],
35
35
  ) -> None:
36
36
  super().__init__()
37
37
  self._key = key
@@ -48,20 +48,24 @@ class InvalidConfigValueTypeError(TypeError):
48
48
  class InvalidConfigValueError(ValueError):
49
49
  def __init__(
50
50
  self,
51
- key: str | Sequence[str] | type,
51
+ key: str | Sequence[str] | None,
52
52
  val: object | None,
53
+ typ: type | Sequence[type],
53
54
  ) -> None:
54
55
  super().__init__()
55
56
  self._key = key
56
57
  self._val = val
58
+ self._typ = typ
57
59
 
58
60
  def __str__(self) -> str:
59
- if isinstance(self._key, type):
60
- return f"invalid config value for type {self._key}: {self._val}"
61
- return f"invalid config value for key {self._key}: {self._val}"
61
+ return (
62
+ f"invalid config value for key {self._key} (type {self._typ}): {self._val}"
63
+ )
62
64
 
63
65
  def __repr__(self) -> str:
64
- return f"InvalidConfigValueError({self._key:!r}, {self._val:!r})"
66
+ return (
67
+ f"InvalidConfigValueError({self._key:!r}, {self._val:!r}, {self._typ:!r})"
68
+ )
65
69
 
66
70
 
67
71
  class MalformedConfigFileError(Exception):
@@ -1,6 +1,6 @@
1
1
  import datetime
2
2
  import sys
3
- from typing import Final, Sequence
3
+ from typing import Final, Sequence, TypeGuard
4
4
 
5
5
  from .errors import (
6
6
  InvalidConfigKeyError,
@@ -43,7 +43,7 @@ def validate_section(section: str) -> None:
43
43
  raise InvalidConfigSectionError(section)
44
44
 
45
45
 
46
- def get_expected_type_for_config_key(key: str | Sequence[str]) -> type:
46
+ def get_expected_type_for_config_key(key: str | Sequence[str]) -> type | Sequence[type]:
47
47
  parsed_key = parse_config_key(key)
48
48
  if len(parsed_key) != 2:
49
49
  # for now there's no nested config option
@@ -87,17 +87,29 @@ def _get_expected_type_for_section_repo(sel: str) -> type:
87
87
  raise InvalidConfigKeyError(sel)
88
88
 
89
89
 
90
- def _get_expected_type_for_section_telemetry(sel: str) -> type:
90
+ def _get_expected_type_for_section_telemetry(sel: str) -> type | tuple[type, ...]:
91
91
  if sel == KEY_TELEMETRY_MODE:
92
92
  return str
93
93
  elif sel == KEY_TELEMETRY_PM_TELEMETRY_URL:
94
94
  return str
95
95
  elif sel == KEY_TELEMETRY_UPLOAD_CONSENT:
96
- return datetime.datetime
96
+ return (type(None), datetime.datetime)
97
97
  else:
98
98
  raise InvalidConfigKeyError(sel)
99
99
 
100
100
 
101
+ def _is_all_str(obj: object) -> TypeGuard[Sequence[str]]:
102
+ if not isinstance(obj, Sequence):
103
+ return False
104
+ return all(isinstance(i, str) for i in obj)
105
+
106
+
107
+ def _is_all_type(obj: object) -> TypeGuard[Sequence[type]]:
108
+ if not isinstance(obj, Sequence):
109
+ return False
110
+ return all(isinstance(i, type) for i in obj)
111
+
112
+
101
113
  def ensure_valid_config_kv(
102
114
  key: str | Sequence[str],
103
115
  check_val: bool = False,
@@ -108,9 +120,9 @@ def ensure_valid_config_kv(
108
120
  # for now there's no nested config option
109
121
  raise InvalidConfigKeyError(key)
110
122
 
111
- expected_type = get_expected_type_for_config_key(parsed_key)
123
+ expected_types = get_expected_type_for_config_key(parsed_key)
112
124
  # validity of config key is already checked by get_expected_type_for_config_key
113
- _ensure_value_type(key, check_val, val, expected_type)
125
+ ensure_value_type(key, check_val, val, expected_types)
114
126
 
115
127
  if not check_val:
116
128
  return
@@ -120,14 +132,26 @@ def ensure_valid_config_kv(
120
132
  return _extra_validate_section_telemetry_kv(key, sel, val)
121
133
 
122
134
 
123
- def _ensure_value_type(
135
+ def ensure_value_type(
124
136
  key: str | Sequence[str],
125
137
  check_val: bool,
126
138
  val: object | None,
127
- expected: type,
139
+ expected: type | Sequence[type],
128
140
  ) -> None:
129
- if check_val and not isinstance(val, expected):
130
- raise InvalidConfigValueTypeError(key, val, expected)
141
+ if not check_val:
142
+ return
143
+
144
+ expected_types: tuple[type, ...]
145
+ if isinstance(expected, type):
146
+ expected_types = (expected,)
147
+ else:
148
+ expected_types = tuple(expected)
149
+
150
+ for ty in expected_types:
151
+ if isinstance(val, ty):
152
+ return
153
+
154
+ raise InvalidConfigValueTypeError(key, val, expected)
131
155
 
132
156
 
133
157
  def _extra_validate_section_telemetry_kv(
@@ -138,7 +162,7 @@ def _extra_validate_section_telemetry_kv(
138
162
  if sel == KEY_TELEMETRY_MODE:
139
163
  # value type is already ensured earlier
140
164
  if val not in ("local", "off", "on"):
141
- raise InvalidConfigValueError(key, val)
165
+ raise InvalidConfigValueError(key, val, str)
142
166
 
143
167
 
144
168
  def encode_value(v: object) -> str:
@@ -169,24 +193,50 @@ def encode_value(v: object) -> str:
169
193
 
170
194
 
171
195
  def decode_value(
172
- key: str | Sequence[str] | type,
196
+ key: str | Sequence[str],
173
197
  val: str,
174
198
  ) -> object:
175
199
  """Decodes the given string representation of a config value into a Python
176
200
  value, directed by type information implied by the config key."""
177
201
 
178
202
  if isinstance(key, type):
179
- expected_type = key
180
- else:
181
- expected_type = get_expected_type_for_config_key(key)
203
+ return _decode_single_type_value(None, val, key)
204
+ elif _is_all_type(key):
205
+ return _decode_typed_value(None, val, key)
206
+
207
+ assert isinstance(key, str) or _is_all_str(key)
208
+ expected_types = get_expected_type_for_config_key(key)
209
+
210
+ if isinstance(expected_types, type):
211
+ return _decode_single_type_value(key, val, expected_types)
212
+ return _decode_typed_value(key, val, expected_types)
213
+
214
+
215
+ def _decode_typed_value(
216
+ key: str | Sequence[str] | None,
217
+ val: str,
218
+ expected_types: Sequence[type],
219
+ ) -> object:
220
+ for ty in expected_types:
221
+ try:
222
+ return _decode_single_type_value(key, val, ty)
223
+ except (RuntimeError, TypeError, ValueError):
224
+ continue
225
+ raise InvalidConfigValueError(key, val, expected_types)
226
+
182
227
 
228
+ def _decode_single_type_value(
229
+ key: str | Sequence[str] | None,
230
+ val: str,
231
+ expected_type: type,
232
+ ) -> object:
183
233
  if expected_type is bool:
184
234
  if val in ("true", "yes", "1"):
185
235
  return True
186
236
  elif val in ("false", "no", "0"):
187
237
  return False
188
238
  else:
189
- raise InvalidConfigValueError(key, val)
239
+ raise InvalidConfigValueError(key, val, expected_type)
190
240
  elif expected_type is int:
191
241
  return int(val, 10)
192
242
  elif expected_type is str:
@@ -199,4 +249,4 @@ def decode_value(
199
249
  v = datetime.datetime.fromisoformat(val)
200
250
  return v.astimezone() if v.tzinfo is None else v
201
251
  else:
202
- raise NotImplementedError(f"invalid type for config value: {expected_type}")
252
+ raise NotImplementedError(f"unhandled type for config value: {expected_type}")
@@ -76,6 +76,16 @@ class RuyipkgGlobalStateStore:
76
76
  """Ensure the state directory exists."""
77
77
  self.root.mkdir(parents=True, exist_ok=True)
78
78
 
79
+ def purge_installation_info(self) -> None:
80
+ """Purge installation records."""
81
+ self._installs_file.unlink(missing_ok=True)
82
+ self._installs_cache = None
83
+ # if the state dir is empty, remove it
84
+ try:
85
+ self.root.rmdir()
86
+ except OSError:
87
+ pass
88
+
79
89
  def _load_installs(self) -> dict[str, PackageInstallationInfo]:
80
90
  """Load installation records from disk."""
81
91
  if self._installs_cache is not None:
@@ -128,6 +128,24 @@ def _guess_porcelain_from_argv(argv: list[str]) -> bool:
128
128
  return len(argv) > 1 and argv[1] == "--porcelain"
129
129
 
130
130
 
131
+ def _probe_cli_autocomplete(env: Mapping[str, str], argv: list[str]) -> bool:
132
+ """
133
+ Probe if the current invocation is related to shell completion based on
134
+ the arguments passed, without requiring the ``argparse`` machinery to be
135
+ completely initialized, and the environment.
136
+ """
137
+
138
+ # If `--output-completion-script` is present anywhere in the arguments,
139
+ # then this is related to shell completion even if _ARGCOMPLETE is not yet
140
+ # set (which is only set for invocations after the shell finished sourcing
141
+ # the completion script).
142
+ for arg in argv:
143
+ if arg.startswith("--output-completion-script"):
144
+ return True
145
+
146
+ return "_ARGCOMPLETE" in os.environ
147
+
148
+
131
149
  class EnvGlobalModeProvider(GlobalModeProvider):
132
150
  def __init__(
133
151
  self,
@@ -150,7 +168,7 @@ class EnvGlobalModeProvider(GlobalModeProvider):
150
168
 
151
169
  # We have to lift this piece of implementation detail out of argcomplete,
152
170
  # as the argcomplete import is very costly in terms of startup time.
153
- self._is_cli_autocomplete = "_ARGCOMPLETE" in os.environ
171
+ self._is_cli_autocomplete = _probe_cli_autocomplete(env, argv)
154
172
 
155
173
  self._venv_root = env.get(ENV_VENV_ROOT_KEY)
156
174
 
File without changes