protoprimer 0.12.0.dev1__tar.gz → 0.12.0.dev2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: protoprimer
3
- Version: 0.12.0.dev1
3
+ Version: 0.12.0.dev2
4
4
  Summary: Escape the fragile mess of pre-venv shell scripts with a pure Python bootstrap wrapper: 🚀 ⏩ single-touch, zero-args, self-contained, no-deps, cross-platform, testable init logic using standard tools to run apps in a custom environment isolated per repository
5
5
  Author-email: uvsmtid <uvsmtid@gmail.com>
6
6
  License: MIT
@@ -52,7 +52,7 @@ from typing import (
52
52
 
53
53
  # The release process ensures that content in this file matches the version below while tagging the release commit
54
54
  # (otherwise, if the file comes from a different commit, the version is irrelevant):
55
- __version__ = "0.12.0.dev1"
55
+ __version__ = "0.12.0.dev2"
56
56
 
57
57
  logger: logging.Logger = logging.getLogger()
58
58
 
@@ -67,8 +67,8 @@ def run_process(env_ctx: EnvContext) -> None:
67
67
  try:
68
68
  ensure_min_python_version()
69
69
 
70
- state_sub_command_executed: bool = env_ctx.state_graph.eval_state(TargetState.target_sub_command_executed.value.name)
71
- assert state_sub_command_executed
70
+ state_everything_executed: bool = env_ctx.state_graph.eval_state(TargetState.target_everything_executed.value.name)
71
+ assert state_everything_executed
72
72
  atexit.register(lambda: env_ctx.print_exit_line(0))
73
73
 
74
74
  except subprocess.CalledProcessError as subproc_error:
@@ -482,6 +482,10 @@ class ValueName(enum.Enum):
482
482
 
483
483
  value_version = "version"
484
484
 
485
+ value_file_basename = "file_basename"
486
+
487
+ value_version_constraints = "version_constraints"
488
+
485
489
 
486
490
  class PathName(enum.Enum):
487
491
 
@@ -645,6 +649,9 @@ class ConfField(enum.Enum):
645
649
 
646
650
  field_install_specs = f"{ValueName.value_install_specs.value}"
647
651
 
652
+ # state_version_constraints_file_basename_inited:
653
+ field_version_constraints_file_basename = f"{ValueName.value_version_constraints.value}_{ValueName.value_file_basename.value}"
654
+
648
655
  ####################################################################################################################
649
656
 
650
657
  # child of `field_project_descriptors`:
@@ -1314,8 +1321,8 @@ class ConfConstGeneral:
1314
1321
  relative_path_field_note: str = f"The path is relative to the `{PathName.path_ref_root.value}` dir specified in the `{ConfField.field_ref_root_dir_rel_path.value}` field."
1315
1322
  common_field_global_note: str = f"This field can be specified in global config (see `{ConfLeap.leap_client.name}`) but it is override-able by local environment-specific config (see `{ConfLeap.leap_env.name}`)."
1316
1323
  common_field_local_note: str = f"This local environment-specific field overrides the global one (see description in `{ConfLeap.leap_client.name}`)."
1317
- func_note_derived_based_on_common = lambda field_name: (f"This value is derived from `{field_name}` in `{ConfLeap.leap_client.name}` (override-able in `{ConfLeap.leap_env.name}`) - see description there.")
1318
- func_note_derived_based_on_conf_leap_field = lambda field_name, conf_leap: (f"This value is derived from `{field_name}` - see description in `{conf_leap.name}`.")
1324
+ func_note_derived_based_on_common = lambda field_name: f"This value is derived from `{field_name}` in `{ConfLeap.leap_client.name}` (override-able in `{ConfLeap.leap_env.name}`) - see description there."
1325
+ func_note_derived_based_on_conf_leap_field = lambda field_name, conf_leap: f"This value is derived from `{field_name}` - see description in `{conf_leap.name}`."
1319
1326
 
1320
1327
 
1321
1328
  class ConfConstInput:
@@ -1416,7 +1423,7 @@ class ConfConstEnv:
1416
1423
 
1417
1424
  default_install_specs = []
1418
1425
 
1419
- constraints_txt_basename = "constraints.txt"
1426
+ default_version_constraints_file_basename = "constraints.txt"
1420
1427
 
1421
1428
  # FT_84_11_73_28.supported_python_versions.md:
1422
1429
  latest_known_python_version = "3.14"
@@ -1542,7 +1549,7 @@ def _create_child_argparser(parent_argparsers):
1542
1549
  parser_check.set_defaults(sub_command=SubCommand.command_check.value)
1543
1550
 
1544
1551
  child_argparser = CustomArgumentParser(
1545
- description=(f"The early [{PrimerRuntime.runtime_proto.value}] environment bootstrapper [{KeyWord.key_primer.value}]."),
1552
+ description=f"The early [{PrimerRuntime.runtime_proto.value}] environment bootstrapper [{KeyWord.key_primer.value}].",
1546
1553
  parents=parent_argparsers,
1547
1554
  epilog=f"Version: {__version__} | {ConfConstGeneral.name_protoprimer_site_link} | {pathlib.Path(__file__).resolve()}",
1548
1555
  )
@@ -1942,45 +1949,58 @@ class Bootstrapper_state_default_stderr_log_handler_configured(AbstractCachingSt
1942
1949
 
1943
1950
 
1944
1951
  # noinspection PyPep8Naming
1945
- @trivial_factory
1946
- class Bootstrapper_state_args_parsed(AbstractCachingStateNode[argparse.Namespace]):
1952
+ @conditional_factory
1953
+ class Bootstrapper_state_args_parsed_func_boot_env(AbstractCachingStateNode[argparse.Namespace]):
1954
+
1955
+ _state_name = staticmethod(lambda: EnvState.state_args_parsed.name)
1956
+
1957
+ def _eval_state_once(self) -> ValueType:
1958
+ return parse_args()
1959
+
1960
+
1961
+ # noinspection PyPep8Naming
1962
+ @conditional_factory
1963
+ class Bootstrapper_state_args_parsed_not_func_boot_env(AbstractCachingStateNode[argparse.Namespace]):
1947
1964
 
1948
1965
  _state_name = staticmethod(lambda: EnvState.state_args_parsed.name)
1949
1966
 
1950
1967
  def _eval_state_once(self) -> ValueType:
1951
- """
1952
- Parse the args if `EntryFunc.func_boot_env` or `EntryFunc.func_run_main`.
1953
- Otherwise, pretend there are no args except `SubCommand.command_start`.
1954
- """
1955
1968
  # TODO: TODO_60_63_68_81.refactor_DAG_builder.md:
1956
1969
  # This step should not be executed without need to parse args.
1957
1970
  # Do not fake args. Just redesign dependency. No?
1958
- state_args_parsed: argparse.Namespace
1959
- if self.env_ctx.graph_coordinates.entry_func == EntryFunc.func_boot_env or self.env_ctx.graph_coordinates.entry_func == EntryFunc.func_run_main:
1960
- state_args_parsed = parse_args()
1961
- else:
1962
- # Pretend there is no args except `SubCommand.command_start`:
1963
- state_args_parsed = parse_args([])
1964
- setattr(
1965
- state_args_parsed,
1966
- ParsedArg.name_sub_command.value,
1967
- SubCommand.command_start.value,
1968
- )
1971
+ # Pretend there is no args except `SubCommand.command_start`:
1972
+ state_args_parsed: argparse.Namespace = parse_args([])
1973
+ setattr(
1974
+ state_args_parsed,
1975
+ ParsedArg.name_sub_command.value,
1976
+ SubCommand.command_start.value,
1977
+ )
1969
1978
  return state_args_parsed
1970
1979
 
1971
1980
 
1981
+ # noinspection PyPep8Naming
1982
+ class Factory_state_args_parsed(NodeFactory[StateStride]):
1983
+
1984
+ def create_state_node(self) -> StateNode[ValueType]:
1985
+ # TODO: TODO_60_63_68_81.refactor_DAG_builder.md:
1986
+ # This step should not be executed without need to parse args.
1987
+ # Do not fake args. Just redesign dependency. No?
1988
+ if self.env_ctx.graph_coordinates.entry_func in [
1989
+ EntryFunc.func_boot_env,
1990
+ EntryFunc.func_run_main,
1991
+ ]:
1992
+ return Bootstrapper_state_args_parsed_func_boot_env(self.env_ctx)
1993
+ else:
1994
+ return Bootstrapper_state_args_parsed_not_func_boot_env(self.env_ctx)
1995
+
1996
+
1972
1997
  # noinspection PyPep8Naming
1973
1998
  @trivial_factory
1974
1999
  class Bootstrapper_state_input_stderr_log_level_eval_finalized(AbstractCachingStateNode[int]):
1975
- """
1976
- There is a narrow window between the default log level is set and this state is evaluated.
1977
- To control the default log level, see `EnvVar.var_PROTOPRIMER_STDERR_LOG_LEVEL`.
1978
- """
1979
2000
 
1980
2001
  _parent_states = staticmethod(
1981
2002
  lambda: [
1982
2003
  EnvState.state_input_stderr_log_level_var_loaded.name,
1983
- EnvState.state_default_stderr_log_handler_configured.name,
1984
2004
  EnvState.state_args_parsed.name,
1985
2005
  ]
1986
2006
  )
@@ -1990,8 +2010,6 @@ class Bootstrapper_state_input_stderr_log_level_eval_finalized(AbstractCachingSt
1990
2010
 
1991
2011
  state_input_stderr_log_level_var_loaded: int = self.eval_parent_state(EnvState.state_input_stderr_log_level_var_loaded.name)
1992
2012
 
1993
- state_default_stderr_logger_configured: logging.Handler = self.eval_parent_state(EnvState.state_default_stderr_log_handler_configured.name)
1994
-
1995
2013
  parsed_args = self.eval_parent_state(EnvState.state_args_parsed.name)
1996
2014
  stderr_log_level_quiet_count: int = getattr(
1997
2015
  parsed_args,
@@ -2002,9 +2020,9 @@ class Bootstrapper_state_input_stderr_log_level_eval_finalized(AbstractCachingSt
2002
2020
  SyntaxArg.dest_verbose,
2003
2021
  )
2004
2022
 
2005
- stderr_log_level_eval_finalized: int
2023
+ state_input_stderr_log_level_eval_finalized: int
2006
2024
  if stderr_log_level_quiet_count == 0 and stderr_log_level_verbose_count == 0:
2007
- stderr_log_level_eval_finalized = state_input_stderr_log_level_var_loaded
2025
+ state_input_stderr_log_level_eval_finalized = state_input_stderr_log_level_var_loaded
2008
2026
  else:
2009
2027
  # FT_38_73_38_52.log_verbosity.md
2010
2028
  # The base is the numeric value of `ConfConstInput.default_PROTOPRIMER_STDERR_LOG_LEVEL`.
@@ -2015,37 +2033,50 @@ class Bootstrapper_state_input_stderr_log_level_eval_finalized(AbstractCachingSt
2015
2033
 
2016
2034
  relative_log_level = 10 * (stderr_log_level_quiet_count - stderr_log_level_verbose_count)
2017
2035
 
2018
- stderr_log_level_eval_finalized = base_log_level + relative_log_level
2036
+ state_input_stderr_log_level_eval_finalized = base_log_level + relative_log_level
2019
2037
 
2020
- if stderr_log_level_eval_finalized < logging.NOTSET:
2021
- stderr_log_level_eval_finalized = logging.NOTSET
2038
+ if state_input_stderr_log_level_eval_finalized < logging.NOTSET:
2039
+ state_input_stderr_log_level_eval_finalized = logging.NOTSET
2040
+
2041
+ return state_input_stderr_log_level_eval_finalized
2022
2042
 
2023
- # TODO: TODO_60_63_68_81.refactor_DAG_builder.md:
2024
- # Evaluate `stderr_log_level_eval_finalized`
2025
- # but do not configure anything for `start_app` and `lib_call`
2026
2043
 
2027
- state_default_stderr_logger_configured.setLevel(stderr_log_level_eval_finalized)
2044
+ # noinspection PyPep8Naming
2045
+ @trivial_factory
2046
+ class Bootstrapper_state_input_stderr_log_level_handler_configured(AbstractCachingStateNode[logging.Handler]):
2047
+ """
2048
+ There is a narrow window between the default log level is set and this state is evaluated.
2049
+ To control the default log level, see `EnvVar.var_PROTOPRIMER_STDERR_LOG_LEVEL`.
2050
+ """
2051
+
2052
+ _parent_states = staticmethod(
2053
+ lambda: [
2054
+ EnvState.state_default_stderr_log_handler_configured.name,
2055
+ EnvState.state_input_stderr_log_level_eval_finalized.name,
2056
+ ]
2057
+ )
2058
+ _state_name = staticmethod(lambda: EnvState.state_input_stderr_log_level_handler_configured.name)
2059
+
2060
+ def _eval_state_once(self) -> ValueType:
2061
+
2062
+ state_default_stderr_logger_configured: logging.Handler = self.eval_parent_state(EnvState.state_default_stderr_log_handler_configured.name)
2063
+
2064
+ state_input_stderr_log_level_eval_finalized: int = self.eval_parent_state(EnvState.state_input_stderr_log_level_eval_finalized.name)
2065
+
2066
+ state_default_stderr_logger_configured.setLevel(state_input_stderr_log_level_eval_finalized)
2028
2067
  assert isinstance(
2029
2068
  state_default_stderr_logger_configured.formatter,
2030
2069
  _PrimerStderrLogFormatter,
2031
2070
  )
2032
- state_default_stderr_logger_configured.formatter.set_verbosity_level(stderr_log_level_eval_finalized)
2033
-
2034
- # Set default log level for subsequent invocations:
2035
- level_var_value: str = logging.getLevelName(stderr_log_level_eval_finalized)
2036
- assert isinstance(level_var_value, str)
2037
- if " " in level_var_value:
2038
- # Due to some hacks in the `python` `logging` library,
2039
- # it may return non-existing level names - use number instead:
2040
- level_var_value = str(stderr_log_level_eval_finalized)
2071
+ state_default_stderr_logger_configured.formatter.set_verbosity_level(state_input_stderr_log_level_eval_finalized)
2041
2072
 
2042
2073
  # Remove stack trace for levels >= WARNING (it will only print the exception itself):
2043
- if stderr_log_level_eval_finalized >= logging.WARNING:
2074
+ if state_input_stderr_log_level_eval_finalized >= logging.WARNING:
2044
2075
  # Avoid changing that in tests - it changes the global state and causes many tests to fail unexpectedly:
2045
2076
  if not is_test_run():
2046
2077
  sys.tracebacklimit = 0
2047
2078
 
2048
- return stderr_log_level_eval_finalized
2079
+ return state_default_stderr_logger_configured
2049
2080
 
2050
2081
 
2051
2082
  # noinspection PyPep8Naming
@@ -2074,13 +2105,12 @@ class Bootstrapper_state_input_sub_command_arg_loaded(AbstractCachingStateNode[S
2074
2105
 
2075
2106
 
2076
2107
  # noinspection PyPep8Naming
2077
- @trivial_factory
2078
- class Bootstrapper_state_input_final_state_eval_finalized(AbstractCachingStateNode[str]):
2108
+ @conditional_factory
2109
+ class Bootstrapper_state_input_final_state_eval_finalized_func_boot_env(AbstractCachingStateNode[str]):
2079
2110
 
2080
2111
  _parent_states = staticmethod(
2081
2112
  lambda: [
2082
2113
  EnvState.state_args_parsed.name,
2083
- EnvState.state_input_sub_command_arg_loaded.name,
2084
2114
  ]
2085
2115
  )
2086
2116
  _state_name = staticmethod(lambda: EnvState.state_input_final_state_eval_finalized.name)
@@ -2088,8 +2118,6 @@ class Bootstrapper_state_input_final_state_eval_finalized(AbstractCachingStateNo
2088
2118
  def _eval_state_once(self) -> ValueType:
2089
2119
  state_args_parsed: argparse.Namespace = self.eval_parent_state(EnvState.state_args_parsed.name)
2090
2120
 
2091
- state_input_sub_command_arg_loaded: SubCommand = self.eval_parent_state(EnvState.state_input_sub_command_arg_loaded.name)
2092
-
2093
2121
  state_input_final_state_eval_finalized: str | None
2094
2122
  state_input_final_state_eval_finalized = getattr(
2095
2123
  state_args_parsed,
@@ -2107,33 +2135,55 @@ class Bootstrapper_state_input_final_state_eval_finalized(AbstractCachingStateNo
2107
2135
  return state_input_final_state_eval_finalized
2108
2136
 
2109
2137
 
2138
+ # noinspection PyPep8Naming
2139
+ @conditional_factory
2140
+ class Bootstrapper_state_input_final_state_eval_finalized_not_func_boot_env(AbstractCachingStateNode[str]):
2141
+
2142
+ _parent_states = staticmethod(lambda: [])
2143
+ _state_name = staticmethod(lambda: EnvState.state_input_final_state_eval_finalized.name)
2144
+
2145
+ def _eval_state_once(self) -> ValueType:
2146
+ state_input_final_state_eval_finalized = self.env_ctx.final_state
2147
+ return state_input_final_state_eval_finalized
2148
+
2149
+
2150
+ # noinspection PyPep8Naming
2151
+ class Factory_state_input_final_state_eval_finalized(NodeFactory[StateStride]):
2152
+
2153
+ def create_state_node(self) -> StateNode[ValueType]:
2154
+ if self.env_ctx.graph_coordinates.entry_func in [
2155
+ EntryFunc.func_boot_env,
2156
+ EntryFunc.func_run_main,
2157
+ ]:
2158
+ return Bootstrapper_state_input_final_state_eval_finalized_func_boot_env(self.env_ctx)
2159
+ else:
2160
+ return Bootstrapper_state_input_final_state_eval_finalized_not_func_boot_env(self.env_ctx)
2161
+
2162
+
2110
2163
  # noinspection PyPep8Naming
2111
2164
  @trivial_factory
2112
- class Bootstrapper_state_sub_command_executed(AbstractCachingStateNode[bool]):
2165
+ class Bootstrapper_state_func_boot_env_executed(AbstractCachingStateNode[bool]):
2113
2166
  """
2114
- This is a special node - it traverses ALL nodes.
2167
+ This is a special node - it traverses ALL nodes for `EntryFunc` cases with parsed args.
2115
2168
 
2116
- BUT: It does not depend on ALL nodes - instead, it uses a sub command strategy implementation.
2169
+ BUT: It does not depend on ALL nodes - instead, re-executes the graph with a new target.
2117
2170
  """
2118
2171
 
2119
2172
  _parent_states = staticmethod(
2120
2173
  lambda: [
2121
- EnvState.state_input_stderr_log_level_eval_finalized.name,
2174
+ EnvState.state_input_stderr_log_level_handler_configured.name,
2122
2175
  EnvState.state_input_sub_command_arg_loaded.name,
2123
2176
  EnvState.state_input_final_state_eval_finalized.name,
2124
2177
  ]
2125
2178
  )
2126
- _state_name = staticmethod(lambda: EnvState.state_sub_command_executed.name)
2179
+ _state_name = staticmethod(lambda: EnvState.state_func_boot_env_executed.name)
2127
2180
 
2128
2181
  def _eval_state_once(self) -> ValueType:
2129
2182
 
2130
- state_input_stderr_log_level_eval_finalized = self.eval_parent_state(EnvState.state_input_stderr_log_level_eval_finalized.name)
2131
- assert state_input_stderr_log_level_eval_finalized >= 0
2183
+ state_input_sub_command_arg_loaded: SubCommand = self.eval_parent_state(EnvState.state_input_sub_command_arg_loaded.name)
2132
2184
 
2133
2185
  state_input_final_state_eval_finalized: str = self.eval_parent_state(EnvState.state_input_final_state_eval_finalized.name)
2134
2186
 
2135
- state_input_sub_command_arg_loaded: SubCommand = self.eval_parent_state(EnvState.state_input_sub_command_arg_loaded.name)
2136
-
2137
2187
  state_node: StateNode = self.env_ctx.state_graph.get_state_node(state_input_final_state_eval_finalized)
2138
2188
 
2139
2189
  selected_strategy: RunStrategy
@@ -2156,6 +2206,111 @@ class Bootstrapper_state_sub_command_executed(AbstractCachingStateNode[bool]):
2156
2206
  return True
2157
2207
 
2158
2208
 
2209
+ # noinspection PyPep8Naming
2210
+ @trivial_factory
2211
+ class Bootstrapper_state_func_start_app_executed(AbstractCachingStateNode[bool]):
2212
+
2213
+ _parent_states = staticmethod(
2214
+ lambda: [
2215
+ EnvState.state_input_stderr_log_level_handler_configured.name,
2216
+ EnvState.state_input_final_state_eval_finalized.name,
2217
+ ]
2218
+ )
2219
+ _state_name = staticmethod(lambda: EnvState.state_func_start_app_executed.name)
2220
+
2221
+ def _eval_state_once(self) -> ValueType:
2222
+ state_input_final_state_eval_finalized: str = self.eval_parent_state(EnvState.state_input_final_state_eval_finalized.name)
2223
+ state_node: StateNode = self.env_ctx.state_graph.get_state_node(state_input_final_state_eval_finalized)
2224
+ state_node.eval_own_state()
2225
+ return True
2226
+
2227
+
2228
+ # noinspection PyPep8Naming
2229
+ @trivial_factory
2230
+ class Bootstrapper_state_func_call_lib_executed(AbstractCachingStateNode[bool]):
2231
+
2232
+ _parent_states = staticmethod(
2233
+ lambda: [
2234
+ EnvState.state_input_final_state_eval_finalized.name,
2235
+ ]
2236
+ )
2237
+ _state_name = staticmethod(lambda: EnvState.state_func_call_lib_executed.name)
2238
+
2239
+ def _eval_state_once(self) -> ValueType:
2240
+ state_input_final_state_eval_finalized: str = self.eval_parent_state(EnvState.state_input_final_state_eval_finalized.name)
2241
+ state_node: StateNode = self.env_ctx.state_graph.get_state_node(state_input_final_state_eval_finalized)
2242
+ state_node.eval_own_state()
2243
+ return True
2244
+
2245
+
2246
+ # noinspection PyPep8Naming
2247
+ @conditional_factory
2248
+ class Bootstrapper_state_everything_executed_func_boot_env(AbstractCachingStateNode[bool]):
2249
+
2250
+ _parent_states = staticmethod(
2251
+ lambda: [
2252
+ EnvState.state_func_boot_env_executed.name,
2253
+ ]
2254
+ )
2255
+ _state_name = staticmethod(lambda: EnvState.state_everything_executed.name)
2256
+
2257
+ def _eval_state_once(self) -> ValueType:
2258
+ state_func_boot_env_executed: bool = self.eval_parent_state(EnvState.state_func_boot_env_executed.name)
2259
+ return state_func_boot_env_executed
2260
+
2261
+
2262
+ # noinspection PyPep8Naming
2263
+ @conditional_factory
2264
+ class Bootstrapper_state_everything_executed_func_start_app(AbstractCachingStateNode[bool]):
2265
+
2266
+ _parent_states = staticmethod(
2267
+ lambda: [
2268
+ EnvState.state_func_start_app_executed.name,
2269
+ ]
2270
+ )
2271
+ _state_name = staticmethod(lambda: EnvState.state_everything_executed.name)
2272
+
2273
+ def _eval_state_once(self) -> ValueType:
2274
+ state_func_start_app_executed: bool = self.eval_parent_state(EnvState.state_func_start_app_executed.name)
2275
+ return state_func_start_app_executed
2276
+
2277
+
2278
+ # noinspection PyPep8Naming
2279
+ @conditional_factory
2280
+ class Bootstrapper_state_everything_executed_func_call_lib(AbstractCachingStateNode[bool]):
2281
+
2282
+ _parent_states = staticmethod(
2283
+ lambda: [
2284
+ EnvState.state_func_call_lib_executed.name,
2285
+ ]
2286
+ )
2287
+ _state_name = staticmethod(lambda: EnvState.state_everything_executed.name)
2288
+
2289
+ def _eval_state_once(self) -> ValueType:
2290
+ state_func_call_lib_executed: bool = self.eval_parent_state(EnvState.state_func_call_lib_executed.name)
2291
+ return state_func_call_lib_executed
2292
+
2293
+
2294
+ # noinspection PyPep8Naming
2295
+ class Factory_state_everything_executed(NodeFactory[StateStride]):
2296
+ """
2297
+ This is a special node - it traverses ALL nodes.
2298
+ """
2299
+
2300
+ def create_state_node(self) -> StateNode[ValueType]:
2301
+ if self.env_ctx.graph_coordinates.entry_func in [
2302
+ EntryFunc.func_boot_env,
2303
+ EntryFunc.func_run_main,
2304
+ ]:
2305
+ return Bootstrapper_state_everything_executed_func_boot_env(self.env_ctx)
2306
+ elif self.env_ctx.graph_coordinates.entry_func == EntryFunc.func_start_app:
2307
+ return Bootstrapper_state_everything_executed_func_start_app(self.env_ctx)
2308
+ elif self.env_ctx.graph_coordinates.entry_func == EntryFunc.func_call_lib:
2309
+ return Bootstrapper_state_everything_executed_func_call_lib(self.env_ctx)
2310
+ else:
2311
+ raise RuntimeError(self.env_ctx.graph_coordinates.entry_func)
2312
+
2313
+
2159
2314
  # noinspection PyPep8Naming
2160
2315
  @trivial_factory
2161
2316
  class Bootstrapper_state_input_start_id_var_loaded(AbstractCachingStateNode[str]):
@@ -2280,7 +2435,6 @@ class Bootstrapper_state_proto_code_file_abs_path_inited_func_call_lib(AbstractC
2280
2435
  class Bootstrapper_state_proto_code_file_abs_path_inited_not_func_call_lib(AbstractCachingStateNode[str]):
2281
2436
  _parent_states = staticmethod(
2282
2437
  lambda: [
2283
- EnvState.state_input_sub_command_arg_loaded.name,
2284
2438
  EnvState.state_input_proto_code_file_abs_path_var_loaded.name,
2285
2439
  EnvState.state_stride_py_arbitrary_reached.name,
2286
2440
  ]
@@ -2289,10 +2443,6 @@ class Bootstrapper_state_proto_code_file_abs_path_inited_not_func_call_lib(Abstr
2289
2443
 
2290
2444
  def _eval_state_once(self) -> ValueType:
2291
2445
 
2292
- state_stride_py_arbitrary_reached: StateStride = self.eval_parent_state(EnvState.state_stride_py_arbitrary_reached.name)
2293
-
2294
- state_input_sub_command_arg_loaded: SubCommand = self.eval_parent_state(EnvState.state_input_sub_command_arg_loaded.name)
2295
-
2296
2446
  state_input_proto_code_file_abs_path_var_loaded: str | None = self.eval_parent_state(EnvState.state_input_proto_code_file_abs_path_var_loaded.name)
2297
2447
 
2298
2448
  assert self.env_ctx.get_stride().value >= StateStride.stride_py_arbitrary.value
@@ -2306,7 +2456,10 @@ class Bootstrapper_state_proto_code_file_abs_path_inited_not_func_call_lib(Abstr
2306
2456
  else:
2307
2457
  log_python_context()
2308
2458
  if os.environ.get(EnvVar.var_PROTOPRIMER_MOCKED_RESTART.value, None) is None:
2309
- if state_input_sub_command_arg_loaded != SubCommand.command_start:
2459
+ if self.env_ctx.graph_coordinates.entry_func in [
2460
+ EntryFunc.func_boot_env,
2461
+ EntryFunc.func_run_main,
2462
+ ]:
2310
2463
  if self.env_ctx.get_stride().value == StateStride.stride_py_arbitrary.value:
2311
2464
  state_proto_code_file_abs_path_inited = os.path.abspath(__file__)
2312
2465
  else:
@@ -2324,7 +2477,7 @@ class Bootstrapper_state_proto_code_file_abs_path_inited_not_func_call_lib(Abstr
2324
2477
  if is_venv():
2325
2478
  logger.warning(f"`sys.executable` [{sys.executable}] for [{StateStride.stride_py_required.name}] evaluated from config should ideally be outside `venv`")
2326
2479
  else:
2327
- # `SubCommand.command_start` relies only on the `EnvVar.var_PROTOPRIMER_PROTO_CODE` env var:
2480
+ # Rely only on the `EnvVar.var_PROTOPRIMER_PROTO_CODE` env var:
2328
2481
  assert state_input_proto_code_file_abs_path_var_loaded is not None
2329
2482
  state_proto_code_file_abs_path_inited = state_input_proto_code_file_abs_path_var_loaded
2330
2483
  else:
@@ -2402,8 +2555,6 @@ class Bootstrapper_state_primer_conf_file_data_loaded(AbstractCachingStateNode[d
2402
2555
 
2403
2556
  _parent_states = staticmethod(
2404
2557
  lambda: [
2405
- EnvState.state_input_stderr_log_level_eval_finalized.name,
2406
- EnvState.state_input_sub_command_arg_loaded.name,
2407
2558
  EnvState.state_proto_code_file_abs_path_inited.name,
2408
2559
  EnvState.state_primer_conf_file_abs_path_inited.name,
2409
2560
  ]
@@ -2564,8 +2715,6 @@ class Bootstrapper_state_client_conf_file_data_loaded(AbstractCachingStateNode[d
2564
2715
 
2565
2716
  _parent_states = staticmethod(
2566
2717
  lambda: [
2567
- EnvState.state_input_stderr_log_level_eval_finalized.name,
2568
- EnvState.state_input_sub_command_arg_loaded.name,
2569
2718
  EnvState.state_global_conf_file_abs_path_inited.name,
2570
2719
  ]
2571
2720
  )
@@ -2600,17 +2749,8 @@ class Bootstrapper_state_client_conf_file_data_loaded(AbstractCachingStateNode[d
2600
2749
 
2601
2750
 
2602
2751
  # noinspection PyPep8Naming
2603
- @trivial_factory
2604
- class Bootstrapper_state_selected_env_dir_rel_path_inited(AbstractCachingStateNode[str]):
2605
-
2606
- _parent_states = staticmethod(
2607
- lambda: [
2608
- EnvState.state_args_parsed.name,
2609
- EnvState.state_ref_root_dir_abs_path_inited.name,
2610
- EnvState.state_client_conf_file_data_loaded.name,
2611
- ]
2612
- )
2613
- _state_name = staticmethod(lambda: EnvState.state_selected_env_dir_rel_path_inited.name)
2752
+ @conditional_factory
2753
+ class Base_state_selected_env_dir_rel_path(AbstractCachingStateNode[str]):
2614
2754
 
2615
2755
  def _eval_state_once(self) -> ValueType:
2616
2756
 
@@ -2641,14 +2781,11 @@ class Bootstrapper_state_selected_env_dir_rel_path_inited(AbstractCachingStateNo
2641
2781
 
2642
2782
  return state_selected_env_dir_rel_path_inited
2643
2783
 
2784
+ def _select_env_conf_dir_any_path(self) -> str | None:
2785
+ raise NotImplementedError()
2786
+
2644
2787
  def _select_client_local_env_dir_any_path(self) -> str | None:
2645
- state_args_parsed: argparse.Namespace = self.eval_parent_state(EnvState.state_args_parsed.name)
2646
- env_conf_dir_any_path: str | None = getattr(
2647
- state_args_parsed,
2648
- ParsedArg.name_selected_env_dir.value,
2649
- # NOTE: The value is only set for `SubCommand.command_boot`, otherwise, this default is used:
2650
- None,
2651
- )
2788
+ env_conf_dir_any_path = self._select_env_conf_dir_any_path()
2652
2789
  if env_conf_dir_any_path is None:
2653
2790
  # Use the default env configured:
2654
2791
  state_client_conf_file_data_loaded: dict = self.eval_parent_state(EnvState.state_client_conf_file_data_loaded.name)
@@ -2696,13 +2833,68 @@ class Bootstrapper_state_selected_env_dir_rel_path_inited(AbstractCachingStateNo
2696
2833
  raise AssertionError(f"`{PathName.path_selected_env.value}` [{client_local_env_dir_any_path}] is relative to neither `{PathName.path_ref_root.value}` [{state_ref_root_dir_abs_path_inited}] nor curr dir [{os.getcwd()}].")
2697
2834
 
2698
2835
 
2836
+ # noinspection PyPep8Naming
2837
+ @conditional_factory
2838
+ class Bootstrapper_state_selected_env_dir_rel_path_inited_func_boot_env(Base_state_selected_env_dir_rel_path):
2839
+
2840
+ _parent_states = staticmethod(
2841
+ lambda: [
2842
+ EnvState.state_args_parsed.name,
2843
+ EnvState.state_ref_root_dir_abs_path_inited.name,
2844
+ EnvState.state_client_conf_file_data_loaded.name,
2845
+ ]
2846
+ )
2847
+ _state_name = staticmethod(lambda: EnvState.state_selected_env_dir_rel_path_inited.name)
2848
+
2849
+ def _select_env_conf_dir_any_path(self) -> str | None:
2850
+ state_args_parsed: argparse.Namespace = self.eval_parent_state(EnvState.state_args_parsed.name)
2851
+ env_conf_dir_any_path: str | None = getattr(
2852
+ state_args_parsed,
2853
+ ParsedArg.name_selected_env_dir.value,
2854
+ # NOTE: The value is only set for `SubCommand.command_boot`, otherwise, this default is used:
2855
+ None,
2856
+ )
2857
+ return env_conf_dir_any_path
2858
+
2859
+
2860
+ # noinspection PyPep8Naming
2861
+ @conditional_factory
2862
+ class Bootstrapper_state_selected_env_dir_rel_path_inited_not_func_boot_env(Base_state_selected_env_dir_rel_path):
2863
+
2864
+ _parent_states = staticmethod(
2865
+ lambda: [
2866
+ EnvState.state_ref_root_dir_abs_path_inited.name,
2867
+ EnvState.state_client_conf_file_data_loaded.name,
2868
+ ]
2869
+ )
2870
+ _state_name = staticmethod(lambda: EnvState.state_selected_env_dir_rel_path_inited.name)
2871
+
2872
+ def _select_env_conf_dir_any_path(self) -> str | None:
2873
+ return None
2874
+
2875
+
2876
+ # noinspection PyPep8Naming
2877
+ class Factory_state_selected_env_dir_rel_path_inited(NodeFactory[StateStride]):
2878
+ """
2879
+ This is a special node - it traverses ALL nodes.
2880
+ """
2881
+
2882
+ def create_state_node(self) -> StateNode[ValueType]:
2883
+ if self.env_ctx.graph_coordinates.entry_func in [
2884
+ EntryFunc.func_boot_env,
2885
+ EntryFunc.func_run_main,
2886
+ ]:
2887
+ return Bootstrapper_state_selected_env_dir_rel_path_inited_func_boot_env(self.env_ctx)
2888
+ else:
2889
+ return Bootstrapper_state_selected_env_dir_rel_path_inited_not_func_boot_env(self.env_ctx)
2890
+
2891
+
2699
2892
  # noinspection PyPep8Naming
2700
2893
  @trivial_factory
2701
2894
  class Bootstrapper_state_local_conf_symlink_abs_path_inited(AbstractCachingStateNode[str]):
2702
2895
 
2703
2896
  _parent_states = staticmethod(
2704
2897
  lambda: [
2705
- EnvState.state_input_sub_command_arg_loaded.name,
2706
2898
  EnvState.state_ref_root_dir_abs_path_inited.name,
2707
2899
  EnvState.state_client_conf_file_data_loaded.name,
2708
2900
  EnvState.state_selected_env_dir_rel_path_inited.name,
@@ -2712,8 +2904,6 @@ class Bootstrapper_state_local_conf_symlink_abs_path_inited(AbstractCachingState
2712
2904
 
2713
2905
  def _eval_state_once(self) -> ValueType:
2714
2906
 
2715
- state_input_sub_command_arg_loaded: SubCommand = self.eval_parent_state(EnvState.state_input_sub_command_arg_loaded.name)
2716
-
2717
2907
  state_ref_root_dir_abs_path_inited: str = self.eval_parent_state(EnvState.state_ref_root_dir_abs_path_inited.name)
2718
2908
 
2719
2909
  state_selected_env_dir_rel_path_inited: str | None = self.eval_parent_state(EnvState.state_selected_env_dir_rel_path_inited.name)
@@ -2743,7 +2933,10 @@ class Bootstrapper_state_local_conf_symlink_abs_path_inited(AbstractCachingState
2743
2933
  if os.path.exists(state_local_conf_symlink_abs_path_inited):
2744
2934
  if os.path.islink(state_local_conf_symlink_abs_path_inited):
2745
2935
  if os.path.isdir(state_local_conf_symlink_abs_path_inited):
2746
- if state_input_sub_command_arg_loaded == SubCommand.command_start:
2936
+ if self.env_ctx.graph_coordinates.entry_func in [
2937
+ EntryFunc.func_start_app,
2938
+ EntryFunc.func_call_lib,
2939
+ ]:
2747
2940
  # Nothing to do:
2748
2941
  pass
2749
2942
  else:
@@ -2803,8 +2996,6 @@ class Bootstrapper_state_env_conf_file_data_loaded(AbstractCachingStateNode[dict
2803
2996
 
2804
2997
  _parent_states = staticmethod(
2805
2998
  lambda: [
2806
- EnvState.state_input_stderr_log_level_eval_finalized.name,
2807
- EnvState.state_input_sub_command_arg_loaded.name,
2808
2999
  EnvState.state_local_conf_file_abs_path_inited.name,
2809
3000
  ]
2810
3001
  )
@@ -3163,6 +3354,25 @@ class Bootstrapper_state_install_specs_inited(AbstractOverriddenFieldCachingStat
3163
3354
  return install_specs
3164
3355
 
3165
3356
 
3357
+ # noinspection PyPep8Naming
3358
+ @trivial_factory
3359
+ class Bootstrapper_state_version_constraints_file_basename_inited(AbstractOverriddenFieldCachingStateNode[str]):
3360
+
3361
+ _parent_states = staticmethod(
3362
+ lambda: [
3363
+ EnvState.state_client_conf_file_data_loaded.name,
3364
+ EnvState.state_env_conf_file_data_loaded.name,
3365
+ ]
3366
+ )
3367
+ _state_name = staticmethod(lambda: EnvState.state_version_constraints_file_basename_inited.name)
3368
+
3369
+ def _eval_state_once(self) -> ValueType:
3370
+ return self._get_overridden_value_or_default(
3371
+ ConfField.field_version_constraints_file_basename.value,
3372
+ ConfConstEnv.default_version_constraints_file_basename,
3373
+ )
3374
+
3375
+
3166
3376
  # noinspection PyPep8Naming
3167
3377
  @trivial_factory
3168
3378
  class Bootstrapper_state_derived_conf_data_loaded(AbstractCachingStateNode[dict]):
@@ -3271,7 +3481,7 @@ class Bootstrapper_state_default_file_log_handler_configured(AbstractCachingStat
3271
3481
  _parent_states = staticmethod(
3272
3482
  lambda: [
3273
3483
  EnvState.state_args_parsed.name,
3274
- EnvState.state_input_stderr_log_level_eval_finalized.name,
3484
+ EnvState.state_input_stderr_log_level_handler_configured.name,
3275
3485
  EnvState.state_input_start_id_var_loaded.name,
3276
3486
  EnvState.state_local_log_dir_abs_path_inited.name,
3277
3487
  ]
@@ -3284,7 +3494,7 @@ class Bootstrapper_state_default_file_log_handler_configured(AbstractCachingStat
3284
3494
 
3285
3495
  state_local_log_dir_abs_path_inited: str = self.eval_parent_state(EnvState.state_local_log_dir_abs_path_inited.name)
3286
3496
 
3287
- state_input_stderr_log_level_eval_finalized: int = self.eval_parent_state(EnvState.state_input_stderr_log_level_eval_finalized.name)
3497
+ state_input_stderr_log_level_handler_configured: logging.Handler = self.eval_parent_state(EnvState.state_input_stderr_log_level_handler_configured.name)
3288
3498
 
3289
3499
  script_path = sys.argv[0]
3290
3500
  script_name = os.path.basename(script_path)
@@ -3292,7 +3502,7 @@ class Bootstrapper_state_default_file_log_handler_configured(AbstractCachingStat
3292
3502
  file_handler = _configure_primer_file_log_handler(
3293
3503
  script_name,
3294
3504
  state_input_start_id_var_loaded,
3295
- state_input_stderr_log_level_eval_finalized,
3505
+ state_input_stderr_log_level_handler_configured,
3296
3506
  state_local_log_dir_abs_path_inited,
3297
3507
  )
3298
3508
 
@@ -3330,14 +3540,6 @@ class Bootstrapper_state_stride_py_required_reached_not_command_start(AbstractCa
3330
3540
  if self.env_ctx.has_stride_reached(next_stride=state_stride_py_required_reached):
3331
3541
  return self.env_ctx.set_max_stride(state_stride_py_required_reached)
3332
3542
 
3333
- state_input_sub_command_arg_loaded: SubCommand = self.eval_parent_state(EnvState.state_input_sub_command_arg_loaded.name)
3334
-
3335
- # TODO: Unused, but plugged in to form complete DAG: consider adding intermediate state to plug it in:
3336
- state_default_file_log_handler_configured = self.eval_parent_state(EnvState.state_default_file_log_handler_configured.name)
3337
-
3338
- # TODO: Unused, but plugged in to form complete DAG: consider adding intermediate state to plug it in:
3339
- state_local_tmp_dir_abs_path_inited = self.eval_parent_state(EnvState.state_local_tmp_dir_abs_path_inited.name)
3340
-
3341
3543
  state_input_start_id_var_loaded: str = self.eval_parent_state(EnvState.state_input_start_id_var_loaded.name)
3342
3544
 
3343
3545
  state_proto_code_file_abs_path_inited: str = self.eval_parent_state(EnvState.state_proto_code_file_abs_path_inited.name)
@@ -3425,7 +3627,7 @@ class Factory_state_stride_py_required_reached(NodeFactory[StateStride]):
3425
3627
  @trivial_factory
3426
3628
  class Bootstrapper_state_reboot_triggered(AbstractCachingStateNode[bool]):
3427
3629
  """
3428
- Removes current `venv` dir and `constraints.txt` file (to trigger their re-creation subsequently).
3630
+ Removes current `venv` dir and `version_constraints.txt` file (to trigger their re-creation subsequently).
3429
3631
  """
3430
3632
 
3431
3633
  _parent_states = staticmethod(
@@ -3437,6 +3639,7 @@ class Bootstrapper_state_reboot_triggered(AbstractCachingStateNode[bool]):
3437
3639
  EnvState.state_local_conf_symlink_abs_path_inited.name,
3438
3640
  EnvState.state_local_venv_dir_abs_path_inited.name,
3439
3641
  EnvState.state_local_tmp_dir_abs_path_inited.name,
3642
+ EnvState.state_version_constraints_file_basename_inited.name,
3440
3643
  EnvState.state_stride_py_required_reached.name,
3441
3644
  ]
3442
3645
  )
@@ -3444,8 +3647,6 @@ class Bootstrapper_state_reboot_triggered(AbstractCachingStateNode[bool]):
3444
3647
 
3445
3648
  def _eval_state_once(self) -> ValueType:
3446
3649
 
3447
- state_args_parsed: argparse.Namespace = self.eval_parent_state(EnvState.state_args_parsed.name)
3448
-
3449
3650
  state_input_sub_command_arg_loaded: SubCommand = self.eval_parent_state(EnvState.state_input_sub_command_arg_loaded.name)
3450
3651
 
3451
3652
  if state_input_sub_command_arg_loaded == SubCommand.command_start:
@@ -3463,8 +3664,6 @@ class Bootstrapper_state_reboot_triggered(AbstractCachingStateNode[bool]):
3463
3664
  state_stride_py_required_reached: StateStride = self.eval_parent_state(EnvState.state_stride_py_required_reached.name)
3464
3665
  assert self.env_ctx.get_stride().value >= StateStride.stride_py_required.value
3465
3666
 
3466
- state_proto_code_file_abs_path_inited: str = self.eval_parent_state(EnvState.state_proto_code_file_abs_path_inited.name)
3467
-
3468
3667
  # Reboot can only happen outside `venv` (to delete it):
3469
3668
  if not (reboot_env and state_stride_py_required_reached == StateStride.stride_py_required):
3470
3669
  return False
@@ -3489,9 +3688,10 @@ class Bootstrapper_state_reboot_triggered(AbstractCachingStateNode[bool]):
3489
3688
  )
3490
3689
 
3491
3690
  state_local_conf_symlink_abs_path_inited = self.eval_parent_state(EnvState.state_local_conf_symlink_abs_path_inited.name)
3691
+ state_version_constraints_file_basename_inited: str = self.eval_parent_state(EnvState.state_version_constraints_file_basename_inited.name)
3492
3692
  constraints_txt_path = os.path.join(
3493
3693
  state_local_conf_symlink_abs_path_inited,
3494
- ConfConstEnv.constraints_txt_basename,
3694
+ state_version_constraints_file_basename_inited,
3495
3695
  )
3496
3696
  if os.path.exists(constraints_txt_path):
3497
3697
  logger.info(f"removing version constraints file [{constraints_txt_path}]")
@@ -3597,8 +3797,6 @@ class Bootstrapper_state_stride_py_venv_reached(AbstractCachingStateNode[StateSt
3597
3797
 
3598
3798
  state_input_start_id_var_loaded: str = self.eval_parent_state(EnvState.state_input_start_id_var_loaded.name)
3599
3799
 
3600
- state_reboot_triggered: bool = self.eval_parent_state(EnvState.state_reboot_triggered.name)
3601
-
3602
3800
  state_proto_code_file_abs_path_inited: str = self.eval_parent_state(EnvState.state_proto_code_file_abs_path_inited.name)
3603
3801
 
3604
3802
  state_selected_python_file_abs_path_inited: str = self.eval_parent_state(EnvState.state_selected_python_file_abs_path_inited.name)
@@ -3668,6 +3866,7 @@ class Bootstrapper_state_protoprimer_package_installed(AbstractCachingStateNode[
3668
3866
  EnvState.state_local_conf_symlink_abs_path_inited.name,
3669
3867
  EnvState.state_project_descriptors_inited.name,
3670
3868
  EnvState.state_install_specs_inited.name,
3869
+ EnvState.state_version_constraints_file_basename_inited.name,
3671
3870
  EnvState.state_venv_driver_prepared.name,
3672
3871
  EnvState.state_stride_py_venv_reached.name,
3673
3872
  ]
@@ -3701,6 +3900,8 @@ class Bootstrapper_state_protoprimer_package_installed(AbstractCachingStateNode[
3701
3900
 
3702
3901
  state_venv_driver_prepared: VenvDriverBase = self.eval_parent_state(EnvState.state_venv_driver_prepared.name)
3703
3902
 
3903
+ state_version_constraints_file_basename_inited: str = self.eval_parent_state(EnvState.state_version_constraints_file_basename_inited.name)
3904
+
3704
3905
  reboot_env: bool = state_args_parsed.sub_command == SubCommand.command_reboot.value
3705
3906
 
3706
3907
  do_install: bool = (
@@ -3714,7 +3915,7 @@ class Bootstrapper_state_protoprimer_package_installed(AbstractCachingStateNode[
3714
3915
 
3715
3916
  constraints_txt_path = os.path.join(
3716
3917
  state_local_conf_symlink_abs_path_inited,
3717
- ConfConstEnv.constraints_txt_basename,
3918
+ state_version_constraints_file_basename_inited,
3718
3919
  )
3719
3920
  if not os.path.exists(constraints_txt_path):
3720
3921
  logger.info(f"creating empty constraints file [{constraints_txt_path}]")
@@ -3806,6 +4007,7 @@ class Bootstrapper_state_version_constraints_generated(AbstractCachingStateNode[
3806
4007
  lambda: [
3807
4008
  EnvState.state_input_sub_command_arg_loaded.name,
3808
4009
  EnvState.state_local_conf_symlink_abs_path_inited.name,
4010
+ EnvState.state_version_constraints_file_basename_inited.name,
3809
4011
  EnvState.state_venv_driver_prepared.name,
3810
4012
  EnvState.state_protoprimer_package_installed.name,
3811
4013
  ]
@@ -3818,7 +4020,7 @@ class Bootstrapper_state_version_constraints_generated(AbstractCachingStateNode[
3818
4020
 
3819
4021
  if state_input_sub_command_arg_loaded == SubCommand.command_start:
3820
4022
  # The only reason for `EnvState.state_version_constraints_generated`
3821
- # is to re-generate the `constraints.txt` file based on `venv`.
4023
+ # is to re-generate the `version_constraints.txt` file based on `venv`.
3822
4024
  # Skip it as `venv` is supposed to be ready in `SubCommand.command_start`:
3823
4025
  return False
3824
4026
 
@@ -3831,11 +4033,13 @@ class Bootstrapper_state_version_constraints_generated(AbstractCachingStateNode[
3831
4033
 
3832
4034
  state_venv_driver_prepared: VenvDriverBase = self.eval_parent_state(EnvState.state_venv_driver_prepared.name)
3833
4035
 
4036
+ state_version_constraints_file_basename_inited: str = self.eval_parent_state(EnvState.state_version_constraints_file_basename_inited.name)
4037
+
3834
4038
  state_venv_driver_prepared.pin_versions(
3835
4039
  get_path_to_curr_python(),
3836
4040
  os.path.join(
3837
4041
  state_local_conf_symlink_abs_path_inited,
3838
- ConfConstEnv.constraints_txt_basename,
4042
+ state_version_constraints_file_basename_inited,
3839
4043
  ),
3840
4044
  )
3841
4045
 
@@ -3875,8 +4079,6 @@ class Bootstrapper_state_stride_deps_updated_reached(AbstractCachingStateNode[St
3875
4079
 
3876
4080
  state_proto_code_file_abs_path_inited: str = self.eval_parent_state(EnvState.state_proto_code_file_abs_path_inited.name)
3877
4081
 
3878
- state_version_constraints_generated: bool = self.eval_parent_state(EnvState.state_version_constraints_generated.name)
3879
-
3880
4082
  state_local_venv_dir_abs_path_inited: str = self.eval_parent_state(EnvState.state_local_venv_dir_abs_path_inited.name)
3881
4083
 
3882
4084
  venv_path_to_python: str = os.path.join(
@@ -3915,7 +4117,6 @@ class Bootstrapper_state_proto_code_updated(AbstractCachingStateNode[bool]):
3915
4117
 
3916
4118
  def _eval_state_once(self) -> ValueType:
3917
4119
 
3918
- state_stride_deps_updated_reached: StateStride = self.eval_parent_state(EnvState.state_stride_deps_updated_reached.name)
3919
4120
  assert self.env_ctx.get_stride().value >= StateStride.stride_deps_updated.value
3920
4121
 
3921
4122
  if self.env_ctx.get_stride().value != StateStride.stride_deps_updated.value:
@@ -4002,15 +4203,11 @@ class Bootstrapper_state_stride_src_updated_reached(AbstractCachingStateNode[Sta
4002
4203
 
4003
4204
  state_stride_src_updated_reached: StateStride = StateStride.stride_src_updated
4004
4205
 
4005
- state_input_sub_command_arg_loaded: SubCommand = self.eval_parent_state(EnvState.state_input_sub_command_arg_loaded.name)
4006
-
4007
4206
  if self.env_ctx.has_stride_reached(next_stride=state_stride_src_updated_reached):
4008
4207
  return self.env_ctx.set_max_stride(state_stride_src_updated_reached)
4009
4208
 
4010
4209
  state_proto_code_file_abs_path_inited: str = self.eval_parent_state(EnvState.state_proto_code_file_abs_path_inited.name)
4011
4210
 
4012
- state_proto_code_updated: bool = self.eval_parent_state(EnvState.state_proto_code_updated.name)
4013
-
4014
4211
  state_local_venv_dir_abs_path_inited: str = self.eval_parent_state(EnvState.state_local_venv_dir_abs_path_inited.name)
4015
4212
 
4016
4213
  venv_path_to_python: str = os.path.join(
@@ -4101,16 +4298,24 @@ class EnvState(enum.Enum):
4101
4298
 
4102
4299
  state_default_stderr_log_handler_configured = Bootstrapper_state_default_stderr_log_handler_configured
4103
4300
 
4104
- state_args_parsed = Bootstrapper_state_args_parsed
4301
+ state_args_parsed = Factory_state_args_parsed
4105
4302
 
4106
4303
  state_input_stderr_log_level_eval_finalized = Bootstrapper_state_input_stderr_log_level_eval_finalized
4107
4304
 
4305
+ state_input_stderr_log_level_handler_configured = Bootstrapper_state_input_stderr_log_level_handler_configured
4306
+
4108
4307
  state_input_sub_command_arg_loaded = Bootstrapper_state_input_sub_command_arg_loaded
4109
4308
 
4110
- state_input_final_state_eval_finalized = Bootstrapper_state_input_final_state_eval_finalized
4309
+ state_input_final_state_eval_finalized = Factory_state_input_final_state_eval_finalized
4310
+
4311
+ state_func_boot_env_executed = Bootstrapper_state_func_boot_env_executed
4312
+
4313
+ state_func_start_app_executed = Bootstrapper_state_func_start_app_executed
4314
+
4315
+ state_func_call_lib_executed = Bootstrapper_state_func_call_lib_executed
4111
4316
 
4112
4317
  # Special case: triggers everything:
4113
- state_sub_command_executed = Bootstrapper_state_sub_command_executed
4318
+ state_everything_executed = Factory_state_everything_executed
4114
4319
 
4115
4320
  state_input_start_id_var_loaded = Bootstrapper_state_input_start_id_var_loaded
4116
4321
 
@@ -4135,7 +4340,7 @@ class EnvState(enum.Enum):
4135
4340
  # `ConfLeap.leap_client`:
4136
4341
  state_client_conf_file_data_loaded = Bootstrapper_state_client_conf_file_data_loaded
4137
4342
 
4138
- state_selected_env_dir_rel_path_inited = Bootstrapper_state_selected_env_dir_rel_path_inited
4343
+ state_selected_env_dir_rel_path_inited = Factory_state_selected_env_dir_rel_path_inited
4139
4344
 
4140
4345
  state_local_conf_symlink_abs_path_inited = Bootstrapper_state_local_conf_symlink_abs_path_inited
4141
4346
 
@@ -4169,6 +4374,8 @@ class EnvState(enum.Enum):
4169
4374
 
4170
4375
  state_install_specs_inited = Bootstrapper_state_install_specs_inited
4171
4376
 
4377
+ state_version_constraints_file_basename_inited = Bootstrapper_state_version_constraints_file_basename_inited
4378
+
4172
4379
  # `ConfLeap.leap_derived`:
4173
4380
  state_derived_conf_data_loaded = Bootstrapper_state_derived_conf_data_loaded
4174
4381
 
@@ -4209,11 +4416,8 @@ class TargetState(enum.Enum):
4209
4416
  Special `EnvState`-s.
4210
4417
  """
4211
4418
 
4212
- # Used for `EnvState.state_status_line_printed` to report exit code:
4213
- target_stderr_log_handler = EnvState.state_default_stderr_log_handler_configured
4214
-
4215
- # A special state that triggers execution of the specific `SubCommand`:
4216
- target_sub_command_executed = EnvState.state_sub_command_executed
4419
+ # A special state that triggers execution of everything else:
4420
+ target_everything_executed = EnvState.state_everything_executed
4217
4421
 
4218
4422
  # FT_00_22_19_59.derived_config.md:
4219
4423
  target_derived_config_loaded = EnvState.state_derived_conf_data_loaded
@@ -4329,14 +4533,13 @@ class MutableValue(Generic[ValueType]):
4329
4533
 
4330
4534
  def set_curr_value(
4331
4535
  self,
4332
- state_node: StateNode | None,
4536
+ state_node: StateNode,
4333
4537
  curr_value: ValueType,
4334
4538
  ) -> None:
4335
4539
  # TODO: Shell we also ensure that the `StateNode` using that `MutableValue` has necessary dependencies on write?
4336
4540
 
4337
4541
  if self.curr_value is None:
4338
4542
  raise AssertionError(f"`{MutableValue.__name__}` [{self.state_name}] cannot be set as it is not initialized yet.")
4339
- state_name: str | None = None
4340
4543
  self.curr_value = curr_value
4341
4544
  logger.debug(f"`{self.__class__.__name__}` [{self.state_name}] `curr_value` after set [{self.curr_value}] in [{state_node.get_state_name()}]")
4342
4545
 
@@ -4614,40 +4817,53 @@ class _PrimerStderrLogFormatter(DefaultStderrLogFormatter):
4614
4817
  }
4615
4818
 
4616
4819
 
4820
+ def _reconfigure_log_handler(
4821
+ formatter_class: type,
4822
+ new_formatter: logging.Formatter,
4823
+ log_level: int,
4824
+ ) -> logging.Handler | None:
4825
+ for log_handler in logger.handlers:
4826
+ if isinstance(log_handler.formatter, formatter_class):
4827
+ # This covers both `_Primer*` (subclass) and `Default*` formatter variants.
4828
+ log_handler.setFormatter(new_formatter)
4829
+ log_handler.setLevel(log_level)
4830
+ # Remove `protoprimer`-specific filter if present:
4831
+ for handler_filter in list(log_handler.filters):
4832
+ if isinstance(handler_filter, StateStrideFilter):
4833
+ log_handler.removeFilter(handler_filter)
4834
+ return log_handler
4835
+ return None
4836
+
4837
+
4617
4838
  def reconfigure_stderr_log_handler(log_level: int = logging.WARNING) -> logging.Handler | None:
4618
4839
  """
4619
4840
  UC_81_50_97_17.reuse_logger.md
4620
4841
  """
4621
-
4622
4842
  logger.setLevel(logging.NOTSET)
4843
+ return _reconfigure_log_handler(
4844
+ formatter_class=DefaultStderrLogFormatter,
4845
+ new_formatter=DefaultStderrLogFormatter(log_level),
4846
+ log_level=log_level,
4847
+ )
4623
4848
 
4624
- # Check for existing primer handler (to replace its formatter):
4625
- stderr_handler: logging.Handler
4626
- for stderr_handler in logger.handlers:
4627
- if isinstance(stderr_handler, logging.StreamHandler) and stderr_handler.stream is sys.stderr and isinstance(stderr_handler.formatter, DefaultStderrLogFormatter):
4628
- # This covers both `_PrimerStderrLogFormatter` (subclass) and `DefaultStderrLogFormatter`.
4629
- stderr_handler.setFormatter(DefaultStderrLogFormatter(log_level))
4630
- stderr_handler.setLevel(log_level)
4631
-
4632
- # Remove primer-specific filter if present:
4633
- for f in list(stderr_handler.filters):
4634
- if isinstance(f, StateStrideFilter):
4635
- stderr_handler.removeFilter(f)
4636
-
4637
- return stderr_handler
4638
4849
 
4850
+ def _find_existing_log_handler(
4851
+ handler_class: type[logging.StreamHandler],
4852
+ formatter_class: type,
4853
+ ) -> logging.StreamHandler | None:
4854
+ """
4855
+ Prevent duplicate handler (when `os.execv*` calls restart `main` again in tests).
4856
+ """
4857
+ if os.environ.get(EnvVar.var_PROTOPRIMER_MOCKED_RESTART.value, None) is None:
4858
+ return None
4859
+ log_handler: logging.Handler
4860
+ for log_handler in logger.handlers:
4861
+ if isinstance(log_handler, handler_class):
4862
+ if isinstance(log_handler.formatter, formatter_class):
4863
+ return log_handler
4639
4864
  return None
4640
4865
 
4641
4866
 
4642
- def configure_default_stderr_log_handler(log_level: int = logging.WARNING) -> logging.Handler:
4643
- logger.setLevel(logging.NOTSET)
4644
- stderr_handler: logging.Handler = logging.StreamHandler(sys.stderr)
4645
- stderr_handler.setFormatter(DefaultStderrLogFormatter(log_level))
4646
- stderr_handler.setLevel(log_level)
4647
- logger.addHandler(stderr_handler)
4648
- return stderr_handler
4649
-
4650
-
4651
4867
  def _configure_primer_stderr_log_handler(state_input_stderr_log_level_var_loaded: int) -> logging.Handler:
4652
4868
  """
4653
4869
  Implements for `stderr` log: FT_38_73_38_52.log_verbosity.md
@@ -4656,23 +4872,16 @@ def _configure_primer_stderr_log_handler(state_input_stderr_log_level_var_loaded
4656
4872
  # Log everything (the filters are supposed to be set on output handlers instead):
4657
4873
  logger.setLevel(logging.NOTSET)
4658
4874
 
4659
- handler_class = logging.StreamHandler
4660
- stderr_handler: logging.Handler | None = None
4661
- if os.environ.get(EnvVar.var_PROTOPRIMER_MOCKED_RESTART.value, None) is not None:
4662
- # Prevent duplicate handler (when `os.execv*` calls restart `main` again in tests).
4663
- # Select `stderr` handler:
4664
- for handler_instance in logger.handlers:
4665
- if isinstance(handler_instance, handler_class):
4666
- if handler_instance.stream is sys.stderr:
4667
- stderr_handler = handler_instance
4668
- break
4875
+ stderr_handler: logging.StreamHandler | None = _find_existing_log_handler(
4876
+ logging.StreamHandler,
4877
+ _PrimerStderrLogFormatter,
4878
+ )
4669
4879
 
4670
4880
  if stderr_handler is None:
4671
- stderr_handler: logging.Handler = handler_class(sys.stderr)
4672
-
4881
+ stderr_handler = logging.StreamHandler(sys.stderr)
4882
+ assert stderr_handler is not None
4673
4883
  stderr_handler.addFilter(StateStrideFilter())
4674
4884
  stderr_handler.setFormatter(_PrimerStderrLogFormatter(state_input_stderr_log_level_var_loaded))
4675
-
4676
4885
  logger.addHandler(stderr_handler)
4677
4886
 
4678
4887
  stderr_handler.setLevel(state_input_stderr_log_level_var_loaded)
@@ -4683,41 +4892,17 @@ def reconfigure_file_log_handler(log_level: int = logging.INFO) -> logging.Handl
4683
4892
  """
4684
4893
  UC_81_50_97_17.reuse_logger.md
4685
4894
  """
4686
-
4687
- # Check for existing `_Primer*` handler (to replace its formatter):
4688
- file_handler: logging.Handler
4689
- for file_handler in logger.handlers:
4690
- if isinstance(file_handler, logging.FileHandler) and isinstance(file_handler.formatter, DefaultFileLogFormatter):
4691
- # This covers both `_PrimerFileLogFormatter` (subclass) and `DefaultFileLogFormatter`.
4692
- file_handler.setFormatter(DefaultFileLogFormatter())
4693
- file_handler.setLevel(log_level)
4694
-
4695
- # Remove primer-specific filter if present:
4696
- for handler_filter in list(file_handler.filters):
4697
- if isinstance(handler_filter, StateStrideFilter):
4698
- file_handler.removeFilter(handler_filter)
4699
-
4700
- return file_handler
4701
-
4702
- return None
4703
-
4704
-
4705
- def configure_default_file_log_handler(
4706
- log_file_abs_path: str,
4707
- log_level: int = logging.INFO,
4708
- ) -> logging.Handler:
4709
- os.makedirs(os.path.dirname(log_file_abs_path), exist_ok=True)
4710
- file_handler: logging.Handler = logging.FileHandler(log_file_abs_path)
4711
- file_handler.setFormatter(DefaultFileLogFormatter())
4712
- file_handler.setLevel(log_level)
4713
- logger.addHandler(file_handler)
4714
- return file_handler
4895
+ return _reconfigure_log_handler(
4896
+ formatter_class=DefaultFileLogFormatter,
4897
+ new_formatter=DefaultFileLogFormatter(),
4898
+ log_level=log_level,
4899
+ )
4715
4900
 
4716
4901
 
4717
4902
  def _configure_primer_file_log_handler(
4718
4903
  script_name: str,
4719
4904
  state_input_start_id_var_loaded: str,
4720
- state_input_stderr_log_level_eval_finalized: int,
4905
+ state_input_stderr_log_level_handler_configured: logging.Handler,
4721
4906
  state_local_log_dir_abs_path_inited: str,
4722
4907
  ) -> logging.Handler:
4723
4908
  """
@@ -4733,33 +4918,37 @@ def _configure_primer_file_log_handler(
4733
4918
  # TODO: Configure MAX file log level in the config file (NOTE: the higher the level the fewer the log entries):
4734
4919
  file_log_level: int = logging.INFO
4735
4920
  # Increase the log level at most to what is used by stderr:
4736
- if state_input_stderr_log_level_eval_finalized < file_log_level:
4737
- file_log_level = state_input_stderr_log_level_eval_finalized
4921
+ if state_input_stderr_log_level_handler_configured.level < file_log_level:
4922
+ file_log_level = state_input_stderr_log_level_handler_configured.level
4738
4923
 
4739
- os.makedirs(
4740
- state_local_log_dir_abs_path_inited,
4741
- exist_ok=True,
4924
+ file_handler: logging.StreamHandler | None = _find_existing_log_handler(
4925
+ logging.FileHandler,
4926
+ _PrimerFileLogFormatter,
4742
4927
  )
4743
4928
 
4744
- if not os.path.exists(log_file_abs_path):
4745
- # Explain missing logs to avoid confusion:
4746
- write_text_file(
4747
- log_file_abs_path,
4748
- f"""
4929
+ if file_handler is None:
4930
+ os.makedirs(
4931
+ state_local_log_dir_abs_path_inited,
4932
+ exist_ok=True,
4933
+ )
4934
+
4935
+ if not os.path.exists(log_file_abs_path):
4936
+ # Explain missing logs to avoid confusion:
4937
+ write_text_file(
4938
+ log_file_abs_path,
4939
+ f"""
4749
4940
  {ConfConstGeneral.log_section_delimiter} file log starts at [{StateStride.stride_py_arbitrary.name}] after its config is resolved {ConfConstGeneral.log_section_delimiter}
4750
4941
 
4751
4942
  """,
4752
- )
4753
-
4754
- file_handler: logging.Handler = logging.FileHandler(log_file_abs_path)
4755
- file_handler.addFilter(StateStrideFilter())
4943
+ )
4756
4944
 
4757
- file_formatter = _PrimerFileLogFormatter()
4945
+ file_handler = logging.FileHandler(log_file_abs_path)
4946
+ assert file_handler is not None
4947
+ file_handler.addFilter(StateStrideFilter())
4948
+ file_handler.setFormatter(_PrimerFileLogFormatter())
4949
+ logger.addHandler(file_handler)
4758
4950
 
4759
4951
  file_handler.setLevel(file_log_level)
4760
- file_handler.setFormatter(file_formatter)
4761
-
4762
- logger.addHandler(file_handler)
4763
4952
  return file_handler
4764
4953
 
4765
4954
 
@@ -5197,7 +5386,7 @@ def select_python_file_abs_path(
5197
5386
  try:
5198
5387
  logger.debug(f"trying `python` version of `selected_python_abs_path` [{selected_python_abs_path}]")
5199
5388
  python_version: tuple[int, int, int] = get_python_version(selected_python_abs_path)
5200
- logger.info(f"`python` version of `selected_python_abs_path` [{selected_python_abs_path}] is [{python_version}]")
5389
+ logger.debug(f"`python` version of `selected_python_abs_path` [{selected_python_abs_path}] is [{python_version}]")
5201
5390
  except (subprocess.CalledProcessError, FileNotFoundError):
5202
5391
  logger.warning(f"`python` in `selected_python_abs_path` [{selected_python_abs_path}] failed without returning its version")
5203
5392
  selected_python_abs_path = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: protoprimer
3
- Version: 0.12.0.dev1
3
+ Version: 0.12.0.dev2
4
4
  Summary: Escape the fragile mess of pre-venv shell scripts with a pure Python bootstrap wrapper: 🚀 ⏩ single-touch, zero-args, self-contained, no-deps, cross-platform, testable init logic using standard tools to run apps in a custom environment isolated per repository
5
5
  Author-email: uvsmtid <uvsmtid@gmail.com>
6
6
  License: MIT
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
 
6
6
  [project]
7
7
  name = "protoprimer"
8
- version = "0.12.0.dev1"
8
+ version = "0.12.0.dev2"
9
9
 
10
10
  authors = [
11
11
  { name = "uvsmtid", email = "uvsmtid@gmail.com" },