protoprimer 0.12.0.dev1__tar.gz → 0.12.1.dev0__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.1.dev0
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.1.dev0"
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
 
@@ -639,6 +643,9 @@ class ConfField(enum.Enum):
639
643
  # state_venv_driver_inited:
640
644
  field_venv_driver = f"{ValueName.value_venv_driver.value}"
641
645
 
646
+ # state_version_constraints_file_basename_inited:
647
+ field_version_constraints_file_basename = f"{ValueName.value_version_constraints.value}_{ValueName.value_file_basename.value}"
648
+
642
649
  # parent of `field_build_root_dir_rel_path` & `field_install_extras`:
643
650
  # state_project_descriptors_inited:
644
651
  field_project_descriptors = f"{ValueName.value_project_descriptors.value}"
@@ -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:
@@ -1406,6 +1413,8 @@ class ConfConstEnv:
1406
1413
  # The default is `uv` only if it is supported by the selected `python` version:
1407
1414
  default_venv_driver = VenvDriverType.venv_uv.name
1408
1415
 
1416
+ default_version_constraints_file_basename = "constraints.txt"
1417
+
1409
1418
  default_project_descriptors = [
1410
1419
  {
1411
1420
  ConfField.field_build_root_dir_rel_path.value: ".",
@@ -1416,8 +1425,6 @@ class ConfConstEnv:
1416
1425
 
1417
1426
  default_install_specs = []
1418
1427
 
1419
- constraints_txt_basename = "constraints.txt"
1420
-
1421
1428
  # FT_84_11_73_28.supported_python_versions.md:
1422
1429
  latest_known_python_version = "3.14"
1423
1430
 
@@ -1463,6 +1470,15 @@ def _create_parent_argparser():
1463
1470
  default=0,
1464
1471
  help="Increase log verbosity level.",
1465
1472
  )
1473
+ parent_argparser.add_argument(
1474
+ SyntaxArg.arg_e,
1475
+ SyntaxArg.arg_env,
1476
+ type=str,
1477
+ default=None,
1478
+ dest=ParsedArg.name_selected_env_dir.value,
1479
+ metavar=ParsedArg.name_selected_env_dir.value,
1480
+ help="Path to the env-specific config dir.",
1481
+ )
1466
1482
  return parent_argparser
1467
1483
 
1468
1484
 
@@ -1475,21 +1491,6 @@ def _create_child_argparser(parent_argparsers):
1475
1491
  description=sub_command_desc,
1476
1492
  )
1477
1493
  parser_boot.set_defaults(sub_command=SubCommand.command_boot.value)
1478
- parser_boot.add_argument(
1479
- SyntaxArg.arg_e,
1480
- SyntaxArg.arg_env,
1481
- type=str,
1482
- default=None,
1483
- dest=ParsedArg.name_selected_env_dir.value,
1484
- metavar=ParsedArg.name_selected_env_dir.value,
1485
- help=(
1486
- f"Path to the env-specific config dir. "
1487
- f"If specified, `{SubCommand.command_boot.value}` sub command creates the symlink to that dir. "
1488
- f"If not specified, the existing symlink is reused. "
1489
- #
1490
- ),
1491
- )
1492
-
1493
1494
  parser_boot.add_argument(
1494
1495
  SyntaxArg.arg_c,
1495
1496
  SyntaxArg.arg_command,
@@ -1542,7 +1543,7 @@ def _create_child_argparser(parent_argparsers):
1542
1543
  parser_check.set_defaults(sub_command=SubCommand.command_check.value)
1543
1544
 
1544
1545
  child_argparser = CustomArgumentParser(
1545
- description=(f"The early [{PrimerRuntime.runtime_proto.value}] environment bootstrapper [{KeyWord.key_primer.value}]."),
1546
+ description=f"The early [{PrimerRuntime.runtime_proto.value}] environment bootstrapper [{KeyWord.key_primer.value}].",
1546
1547
  parents=parent_argparsers,
1547
1548
  epilog=f"Version: {__version__} | {ConfConstGeneral.name_protoprimer_site_link} | {pathlib.Path(__file__).resolve()}",
1548
1549
  )
@@ -1942,45 +1943,58 @@ class Bootstrapper_state_default_stderr_log_handler_configured(AbstractCachingSt
1942
1943
 
1943
1944
 
1944
1945
  # noinspection PyPep8Naming
1945
- @trivial_factory
1946
- class Bootstrapper_state_args_parsed(AbstractCachingStateNode[argparse.Namespace]):
1946
+ @conditional_factory
1947
+ class Bootstrapper_state_args_parsed_func_boot_env(AbstractCachingStateNode[argparse.Namespace]):
1948
+
1949
+ _state_name = staticmethod(lambda: EnvState.state_args_parsed.name)
1950
+
1951
+ def _eval_state_once(self) -> ValueType:
1952
+ return parse_args()
1953
+
1954
+
1955
+ # noinspection PyPep8Naming
1956
+ @conditional_factory
1957
+ class Bootstrapper_state_args_parsed_not_func_boot_env(AbstractCachingStateNode[argparse.Namespace]):
1947
1958
 
1948
1959
  _state_name = staticmethod(lambda: EnvState.state_args_parsed.name)
1949
1960
 
1950
1961
  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
1962
  # TODO: TODO_60_63_68_81.refactor_DAG_builder.md:
1956
1963
  # This step should not be executed without need to parse args.
1957
1964
  # 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
- )
1965
+ # Pretend there is no args except `SubCommand.command_start`:
1966
+ state_args_parsed: argparse.Namespace = parse_args([])
1967
+ setattr(
1968
+ state_args_parsed,
1969
+ ParsedArg.name_sub_command.value,
1970
+ SubCommand.command_start.value,
1971
+ )
1969
1972
  return state_args_parsed
1970
1973
 
1971
1974
 
1975
+ # noinspection PyPep8Naming
1976
+ class Factory_state_args_parsed(NodeFactory[StateStride]):
1977
+
1978
+ def create_state_node(self) -> StateNode[ValueType]:
1979
+ # TODO: TODO_60_63_68_81.refactor_DAG_builder.md:
1980
+ # This step should not be executed without need to parse args.
1981
+ # Do not fake args. Just redesign dependency. No?
1982
+ if self.env_ctx.graph_coordinates.entry_func in [
1983
+ EntryFunc.func_boot_env,
1984
+ EntryFunc.func_run_main,
1985
+ ]:
1986
+ return Bootstrapper_state_args_parsed_func_boot_env(self.env_ctx)
1987
+ else:
1988
+ return Bootstrapper_state_args_parsed_not_func_boot_env(self.env_ctx)
1989
+
1990
+
1972
1991
  # noinspection PyPep8Naming
1973
1992
  @trivial_factory
1974
1993
  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
1994
 
1980
1995
  _parent_states = staticmethod(
1981
1996
  lambda: [
1982
1997
  EnvState.state_input_stderr_log_level_var_loaded.name,
1983
- EnvState.state_default_stderr_log_handler_configured.name,
1984
1998
  EnvState.state_args_parsed.name,
1985
1999
  ]
1986
2000
  )
@@ -1990,8 +2004,6 @@ class Bootstrapper_state_input_stderr_log_level_eval_finalized(AbstractCachingSt
1990
2004
 
1991
2005
  state_input_stderr_log_level_var_loaded: int = self.eval_parent_state(EnvState.state_input_stderr_log_level_var_loaded.name)
1992
2006
 
1993
- state_default_stderr_logger_configured: logging.Handler = self.eval_parent_state(EnvState.state_default_stderr_log_handler_configured.name)
1994
-
1995
2007
  parsed_args = self.eval_parent_state(EnvState.state_args_parsed.name)
1996
2008
  stderr_log_level_quiet_count: int = getattr(
1997
2009
  parsed_args,
@@ -2002,9 +2014,9 @@ class Bootstrapper_state_input_stderr_log_level_eval_finalized(AbstractCachingSt
2002
2014
  SyntaxArg.dest_verbose,
2003
2015
  )
2004
2016
 
2005
- stderr_log_level_eval_finalized: int
2017
+ state_input_stderr_log_level_eval_finalized: int
2006
2018
  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
2019
+ state_input_stderr_log_level_eval_finalized = state_input_stderr_log_level_var_loaded
2008
2020
  else:
2009
2021
  # FT_38_73_38_52.log_verbosity.md
2010
2022
  # The base is the numeric value of `ConfConstInput.default_PROTOPRIMER_STDERR_LOG_LEVEL`.
@@ -2015,37 +2027,50 @@ class Bootstrapper_state_input_stderr_log_level_eval_finalized(AbstractCachingSt
2015
2027
 
2016
2028
  relative_log_level = 10 * (stderr_log_level_quiet_count - stderr_log_level_verbose_count)
2017
2029
 
2018
- stderr_log_level_eval_finalized = base_log_level + relative_log_level
2030
+ state_input_stderr_log_level_eval_finalized = base_log_level + relative_log_level
2019
2031
 
2020
- if stderr_log_level_eval_finalized < logging.NOTSET:
2021
- stderr_log_level_eval_finalized = logging.NOTSET
2032
+ if state_input_stderr_log_level_eval_finalized < logging.NOTSET:
2033
+ state_input_stderr_log_level_eval_finalized = logging.NOTSET
2022
2034
 
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`
2035
+ return state_input_stderr_log_level_eval_finalized
2036
+
2037
+
2038
+ # noinspection PyPep8Naming
2039
+ @trivial_factory
2040
+ class Bootstrapper_state_input_stderr_log_level_handler_configured(AbstractCachingStateNode[logging.Handler]):
2041
+ """
2042
+ There is a narrow window between the default log level is set and this state is evaluated.
2043
+ To control the default log level, see `EnvVar.var_PROTOPRIMER_STDERR_LOG_LEVEL`.
2044
+ """
2045
+
2046
+ _parent_states = staticmethod(
2047
+ lambda: [
2048
+ EnvState.state_default_stderr_log_handler_configured.name,
2049
+ EnvState.state_input_stderr_log_level_eval_finalized.name,
2050
+ ]
2051
+ )
2052
+ _state_name = staticmethod(lambda: EnvState.state_input_stderr_log_level_handler_configured.name)
2053
+
2054
+ def _eval_state_once(self) -> ValueType:
2026
2055
 
2027
- state_default_stderr_logger_configured.setLevel(stderr_log_level_eval_finalized)
2056
+ state_default_stderr_logger_configured: logging.Handler = self.eval_parent_state(EnvState.state_default_stderr_log_handler_configured.name)
2057
+
2058
+ state_input_stderr_log_level_eval_finalized: int = self.eval_parent_state(EnvState.state_input_stderr_log_level_eval_finalized.name)
2059
+
2060
+ state_default_stderr_logger_configured.setLevel(state_input_stderr_log_level_eval_finalized)
2028
2061
  assert isinstance(
2029
2062
  state_default_stderr_logger_configured.formatter,
2030
2063
  _PrimerStderrLogFormatter,
2031
2064
  )
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)
2065
+ state_default_stderr_logger_configured.formatter.set_verbosity_level(state_input_stderr_log_level_eval_finalized)
2041
2066
 
2042
2067
  # Remove stack trace for levels >= WARNING (it will only print the exception itself):
2043
- if stderr_log_level_eval_finalized >= logging.WARNING:
2068
+ if state_input_stderr_log_level_eval_finalized >= logging.WARNING:
2044
2069
  # Avoid changing that in tests - it changes the global state and causes many tests to fail unexpectedly:
2045
2070
  if not is_test_run():
2046
2071
  sys.tracebacklimit = 0
2047
2072
 
2048
- return stderr_log_level_eval_finalized
2073
+ return state_default_stderr_logger_configured
2049
2074
 
2050
2075
 
2051
2076
  # noinspection PyPep8Naming
@@ -2074,13 +2099,12 @@ class Bootstrapper_state_input_sub_command_arg_loaded(AbstractCachingStateNode[S
2074
2099
 
2075
2100
 
2076
2101
  # noinspection PyPep8Naming
2077
- @trivial_factory
2078
- class Bootstrapper_state_input_final_state_eval_finalized(AbstractCachingStateNode[str]):
2102
+ @conditional_factory
2103
+ class Bootstrapper_state_input_final_state_eval_finalized_func_boot_env(AbstractCachingStateNode[str]):
2079
2104
 
2080
2105
  _parent_states = staticmethod(
2081
2106
  lambda: [
2082
2107
  EnvState.state_args_parsed.name,
2083
- EnvState.state_input_sub_command_arg_loaded.name,
2084
2108
  ]
2085
2109
  )
2086
2110
  _state_name = staticmethod(lambda: EnvState.state_input_final_state_eval_finalized.name)
@@ -2088,8 +2112,6 @@ class Bootstrapper_state_input_final_state_eval_finalized(AbstractCachingStateNo
2088
2112
  def _eval_state_once(self) -> ValueType:
2089
2113
  state_args_parsed: argparse.Namespace = self.eval_parent_state(EnvState.state_args_parsed.name)
2090
2114
 
2091
- state_input_sub_command_arg_loaded: SubCommand = self.eval_parent_state(EnvState.state_input_sub_command_arg_loaded.name)
2092
-
2093
2115
  state_input_final_state_eval_finalized: str | None
2094
2116
  state_input_final_state_eval_finalized = getattr(
2095
2117
  state_args_parsed,
@@ -2107,33 +2129,55 @@ class Bootstrapper_state_input_final_state_eval_finalized(AbstractCachingStateNo
2107
2129
  return state_input_final_state_eval_finalized
2108
2130
 
2109
2131
 
2132
+ # noinspection PyPep8Naming
2133
+ @conditional_factory
2134
+ class Bootstrapper_state_input_final_state_eval_finalized_not_func_boot_env(AbstractCachingStateNode[str]):
2135
+
2136
+ _parent_states = staticmethod(lambda: [])
2137
+ _state_name = staticmethod(lambda: EnvState.state_input_final_state_eval_finalized.name)
2138
+
2139
+ def _eval_state_once(self) -> ValueType:
2140
+ state_input_final_state_eval_finalized = self.env_ctx.final_state
2141
+ return state_input_final_state_eval_finalized
2142
+
2143
+
2144
+ # noinspection PyPep8Naming
2145
+ class Factory_state_input_final_state_eval_finalized(NodeFactory[StateStride]):
2146
+
2147
+ def create_state_node(self) -> StateNode[ValueType]:
2148
+ if self.env_ctx.graph_coordinates.entry_func in [
2149
+ EntryFunc.func_boot_env,
2150
+ EntryFunc.func_run_main,
2151
+ ]:
2152
+ return Bootstrapper_state_input_final_state_eval_finalized_func_boot_env(self.env_ctx)
2153
+ else:
2154
+ return Bootstrapper_state_input_final_state_eval_finalized_not_func_boot_env(self.env_ctx)
2155
+
2156
+
2110
2157
  # noinspection PyPep8Naming
2111
2158
  @trivial_factory
2112
- class Bootstrapper_state_sub_command_executed(AbstractCachingStateNode[bool]):
2159
+ class Bootstrapper_state_func_boot_env_executed(AbstractCachingStateNode[bool]):
2113
2160
  """
2114
- This is a special node - it traverses ALL nodes.
2161
+ This is a special node - it traverses ALL nodes for `EntryFunc` cases with parsed args.
2115
2162
 
2116
- BUT: It does not depend on ALL nodes - instead, it uses a sub command strategy implementation.
2163
+ BUT: It does not depend on ALL nodes - instead, re-executes the graph with a new target.
2117
2164
  """
2118
2165
 
2119
2166
  _parent_states = staticmethod(
2120
2167
  lambda: [
2121
- EnvState.state_input_stderr_log_level_eval_finalized.name,
2168
+ EnvState.state_input_stderr_log_level_handler_configured.name,
2122
2169
  EnvState.state_input_sub_command_arg_loaded.name,
2123
2170
  EnvState.state_input_final_state_eval_finalized.name,
2124
2171
  ]
2125
2172
  )
2126
- _state_name = staticmethod(lambda: EnvState.state_sub_command_executed.name)
2173
+ _state_name = staticmethod(lambda: EnvState.state_func_boot_env_executed.name)
2127
2174
 
2128
2175
  def _eval_state_once(self) -> ValueType:
2129
2176
 
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
2177
+ state_input_sub_command_arg_loaded: SubCommand = self.eval_parent_state(EnvState.state_input_sub_command_arg_loaded.name)
2132
2178
 
2133
2179
  state_input_final_state_eval_finalized: str = self.eval_parent_state(EnvState.state_input_final_state_eval_finalized.name)
2134
2180
 
2135
- state_input_sub_command_arg_loaded: SubCommand = self.eval_parent_state(EnvState.state_input_sub_command_arg_loaded.name)
2136
-
2137
2181
  state_node: StateNode = self.env_ctx.state_graph.get_state_node(state_input_final_state_eval_finalized)
2138
2182
 
2139
2183
  selected_strategy: RunStrategy
@@ -2156,6 +2200,111 @@ class Bootstrapper_state_sub_command_executed(AbstractCachingStateNode[bool]):
2156
2200
  return True
2157
2201
 
2158
2202
 
2203
+ # noinspection PyPep8Naming
2204
+ @trivial_factory
2205
+ class Bootstrapper_state_func_start_app_executed(AbstractCachingStateNode[bool]):
2206
+
2207
+ _parent_states = staticmethod(
2208
+ lambda: [
2209
+ EnvState.state_input_stderr_log_level_handler_configured.name,
2210
+ EnvState.state_input_final_state_eval_finalized.name,
2211
+ ]
2212
+ )
2213
+ _state_name = staticmethod(lambda: EnvState.state_func_start_app_executed.name)
2214
+
2215
+ def _eval_state_once(self) -> ValueType:
2216
+ state_input_final_state_eval_finalized: str = self.eval_parent_state(EnvState.state_input_final_state_eval_finalized.name)
2217
+ state_node: StateNode = self.env_ctx.state_graph.get_state_node(state_input_final_state_eval_finalized)
2218
+ state_node.eval_own_state()
2219
+ return True
2220
+
2221
+
2222
+ # noinspection PyPep8Naming
2223
+ @trivial_factory
2224
+ class Bootstrapper_state_func_call_lib_executed(AbstractCachingStateNode[bool]):
2225
+
2226
+ _parent_states = staticmethod(
2227
+ lambda: [
2228
+ EnvState.state_input_final_state_eval_finalized.name,
2229
+ ]
2230
+ )
2231
+ _state_name = staticmethod(lambda: EnvState.state_func_call_lib_executed.name)
2232
+
2233
+ def _eval_state_once(self) -> ValueType:
2234
+ state_input_final_state_eval_finalized: str = self.eval_parent_state(EnvState.state_input_final_state_eval_finalized.name)
2235
+ state_node: StateNode = self.env_ctx.state_graph.get_state_node(state_input_final_state_eval_finalized)
2236
+ state_node.eval_own_state()
2237
+ return True
2238
+
2239
+
2240
+ # noinspection PyPep8Naming
2241
+ @conditional_factory
2242
+ class Bootstrapper_state_everything_executed_func_boot_env(AbstractCachingStateNode[bool]):
2243
+
2244
+ _parent_states = staticmethod(
2245
+ lambda: [
2246
+ EnvState.state_func_boot_env_executed.name,
2247
+ ]
2248
+ )
2249
+ _state_name = staticmethod(lambda: EnvState.state_everything_executed.name)
2250
+
2251
+ def _eval_state_once(self) -> ValueType:
2252
+ state_func_boot_env_executed: bool = self.eval_parent_state(EnvState.state_func_boot_env_executed.name)
2253
+ return state_func_boot_env_executed
2254
+
2255
+
2256
+ # noinspection PyPep8Naming
2257
+ @conditional_factory
2258
+ class Bootstrapper_state_everything_executed_func_start_app(AbstractCachingStateNode[bool]):
2259
+
2260
+ _parent_states = staticmethod(
2261
+ lambda: [
2262
+ EnvState.state_func_start_app_executed.name,
2263
+ ]
2264
+ )
2265
+ _state_name = staticmethod(lambda: EnvState.state_everything_executed.name)
2266
+
2267
+ def _eval_state_once(self) -> ValueType:
2268
+ state_func_start_app_executed: bool = self.eval_parent_state(EnvState.state_func_start_app_executed.name)
2269
+ return state_func_start_app_executed
2270
+
2271
+
2272
+ # noinspection PyPep8Naming
2273
+ @conditional_factory
2274
+ class Bootstrapper_state_everything_executed_func_call_lib(AbstractCachingStateNode[bool]):
2275
+
2276
+ _parent_states = staticmethod(
2277
+ lambda: [
2278
+ EnvState.state_func_call_lib_executed.name,
2279
+ ]
2280
+ )
2281
+ _state_name = staticmethod(lambda: EnvState.state_everything_executed.name)
2282
+
2283
+ def _eval_state_once(self) -> ValueType:
2284
+ state_func_call_lib_executed: bool = self.eval_parent_state(EnvState.state_func_call_lib_executed.name)
2285
+ return state_func_call_lib_executed
2286
+
2287
+
2288
+ # noinspection PyPep8Naming
2289
+ class Factory_state_everything_executed(NodeFactory[StateStride]):
2290
+ """
2291
+ This is a special node - it traverses ALL nodes.
2292
+ """
2293
+
2294
+ def create_state_node(self) -> StateNode[ValueType]:
2295
+ if self.env_ctx.graph_coordinates.entry_func in [
2296
+ EntryFunc.func_boot_env,
2297
+ EntryFunc.func_run_main,
2298
+ ]:
2299
+ return Bootstrapper_state_everything_executed_func_boot_env(self.env_ctx)
2300
+ elif self.env_ctx.graph_coordinates.entry_func == EntryFunc.func_start_app:
2301
+ return Bootstrapper_state_everything_executed_func_start_app(self.env_ctx)
2302
+ elif self.env_ctx.graph_coordinates.entry_func == EntryFunc.func_call_lib:
2303
+ return Bootstrapper_state_everything_executed_func_call_lib(self.env_ctx)
2304
+ else:
2305
+ raise RuntimeError(self.env_ctx.graph_coordinates.entry_func)
2306
+
2307
+
2159
2308
  # noinspection PyPep8Naming
2160
2309
  @trivial_factory
2161
2310
  class Bootstrapper_state_input_start_id_var_loaded(AbstractCachingStateNode[str]):
@@ -2280,7 +2429,6 @@ class Bootstrapper_state_proto_code_file_abs_path_inited_func_call_lib(AbstractC
2280
2429
  class Bootstrapper_state_proto_code_file_abs_path_inited_not_func_call_lib(AbstractCachingStateNode[str]):
2281
2430
  _parent_states = staticmethod(
2282
2431
  lambda: [
2283
- EnvState.state_input_sub_command_arg_loaded.name,
2284
2432
  EnvState.state_input_proto_code_file_abs_path_var_loaded.name,
2285
2433
  EnvState.state_stride_py_arbitrary_reached.name,
2286
2434
  ]
@@ -2289,10 +2437,6 @@ class Bootstrapper_state_proto_code_file_abs_path_inited_not_func_call_lib(Abstr
2289
2437
 
2290
2438
  def _eval_state_once(self) -> ValueType:
2291
2439
 
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
2440
  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
2441
 
2298
2442
  assert self.env_ctx.get_stride().value >= StateStride.stride_py_arbitrary.value
@@ -2306,7 +2450,10 @@ class Bootstrapper_state_proto_code_file_abs_path_inited_not_func_call_lib(Abstr
2306
2450
  else:
2307
2451
  log_python_context()
2308
2452
  if os.environ.get(EnvVar.var_PROTOPRIMER_MOCKED_RESTART.value, None) is None:
2309
- if state_input_sub_command_arg_loaded != SubCommand.command_start:
2453
+ if self.env_ctx.graph_coordinates.entry_func in [
2454
+ EntryFunc.func_boot_env,
2455
+ EntryFunc.func_run_main,
2456
+ ]:
2310
2457
  if self.env_ctx.get_stride().value == StateStride.stride_py_arbitrary.value:
2311
2458
  state_proto_code_file_abs_path_inited = os.path.abspath(__file__)
2312
2459
  else:
@@ -2324,7 +2471,7 @@ class Bootstrapper_state_proto_code_file_abs_path_inited_not_func_call_lib(Abstr
2324
2471
  if is_venv():
2325
2472
  logger.warning(f"`sys.executable` [{sys.executable}] for [{StateStride.stride_py_required.name}] evaluated from config should ideally be outside `venv`")
2326
2473
  else:
2327
- # `SubCommand.command_start` relies only on the `EnvVar.var_PROTOPRIMER_PROTO_CODE` env var:
2474
+ # Rely only on the `EnvVar.var_PROTOPRIMER_PROTO_CODE` env var:
2328
2475
  assert state_input_proto_code_file_abs_path_var_loaded is not None
2329
2476
  state_proto_code_file_abs_path_inited = state_input_proto_code_file_abs_path_var_loaded
2330
2477
  else:
@@ -2402,8 +2549,6 @@ class Bootstrapper_state_primer_conf_file_data_loaded(AbstractCachingStateNode[d
2402
2549
 
2403
2550
  _parent_states = staticmethod(
2404
2551
  lambda: [
2405
- EnvState.state_input_stderr_log_level_eval_finalized.name,
2406
- EnvState.state_input_sub_command_arg_loaded.name,
2407
2552
  EnvState.state_proto_code_file_abs_path_inited.name,
2408
2553
  EnvState.state_primer_conf_file_abs_path_inited.name,
2409
2554
  ]
@@ -2564,8 +2709,6 @@ class Bootstrapper_state_client_conf_file_data_loaded(AbstractCachingStateNode[d
2564
2709
 
2565
2710
  _parent_states = staticmethod(
2566
2711
  lambda: [
2567
- EnvState.state_input_stderr_log_level_eval_finalized.name,
2568
- EnvState.state_input_sub_command_arg_loaded.name,
2569
2712
  EnvState.state_global_conf_file_abs_path_inited.name,
2570
2713
  ]
2571
2714
  )
@@ -2600,17 +2743,8 @@ class Bootstrapper_state_client_conf_file_data_loaded(AbstractCachingStateNode[d
2600
2743
 
2601
2744
 
2602
2745
  # 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)
2746
+ @conditional_factory
2747
+ class Base_state_selected_env_dir_rel_path(AbstractCachingStateNode[str]):
2614
2748
 
2615
2749
  def _eval_state_once(self) -> ValueType:
2616
2750
 
@@ -2641,14 +2775,14 @@ class Bootstrapper_state_selected_env_dir_rel_path_inited(AbstractCachingStateNo
2641
2775
 
2642
2776
  return state_selected_env_dir_rel_path_inited
2643
2777
 
2778
+ def _select_env_conf_dir_any_path(self) -> str | None:
2779
+ raise NotImplementedError()
2780
+
2644
2781
  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
- )
2782
+ """
2783
+ TODO: TODO_41_10_50_01.implement_env_selector.md
2784
+ """
2785
+ env_conf_dir_any_path = self._select_env_conf_dir_any_path()
2652
2786
  if env_conf_dir_any_path is None:
2653
2787
  # Use the default env configured:
2654
2788
  state_client_conf_file_data_loaded: dict = self.eval_parent_state(EnvState.state_client_conf_file_data_loaded.name)
@@ -2696,13 +2830,65 @@ class Bootstrapper_state_selected_env_dir_rel_path_inited(AbstractCachingStateNo
2696
2830
  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
2831
 
2698
2832
 
2833
+ # noinspection PyPep8Naming
2834
+ @conditional_factory
2835
+ class Bootstrapper_state_selected_env_dir_rel_path_inited_func_boot_env(Base_state_selected_env_dir_rel_path):
2836
+
2837
+ _parent_states = staticmethod(
2838
+ lambda: [
2839
+ EnvState.state_args_parsed.name,
2840
+ EnvState.state_ref_root_dir_abs_path_inited.name,
2841
+ EnvState.state_client_conf_file_data_loaded.name,
2842
+ ]
2843
+ )
2844
+ _state_name = staticmethod(lambda: EnvState.state_selected_env_dir_rel_path_inited.name)
2845
+
2846
+ def _select_env_conf_dir_any_path(self) -> str | None:
2847
+ state_args_parsed: argparse.Namespace = self.eval_parent_state(EnvState.state_args_parsed.name)
2848
+ env_conf_dir_any_path: str | None = getattr(
2849
+ state_args_parsed,
2850
+ ParsedArg.name_selected_env_dir.value,
2851
+ # NOTE: The value is only set for `SubCommand.command_boot`, otherwise, this default is used:
2852
+ None,
2853
+ )
2854
+ return env_conf_dir_any_path
2855
+
2856
+
2857
+ # noinspection PyPep8Naming
2858
+ @conditional_factory
2859
+ class Bootstrapper_state_selected_env_dir_rel_path_inited_not_func_boot_env(Base_state_selected_env_dir_rel_path):
2860
+
2861
+ _parent_states = staticmethod(
2862
+ lambda: [
2863
+ EnvState.state_ref_root_dir_abs_path_inited.name,
2864
+ EnvState.state_client_conf_file_data_loaded.name,
2865
+ ]
2866
+ )
2867
+ _state_name = staticmethod(lambda: EnvState.state_selected_env_dir_rel_path_inited.name)
2868
+
2869
+ def _select_env_conf_dir_any_path(self) -> str | None:
2870
+ return None
2871
+
2872
+
2873
+ # noinspection PyPep8Naming
2874
+ class Factory_state_selected_env_dir_rel_path_inited(NodeFactory[StateStride]):
2875
+
2876
+ def create_state_node(self) -> StateNode[ValueType]:
2877
+ if self.env_ctx.graph_coordinates.entry_func in [
2878
+ EntryFunc.func_boot_env,
2879
+ EntryFunc.func_run_main,
2880
+ ]:
2881
+ return Bootstrapper_state_selected_env_dir_rel_path_inited_func_boot_env(self.env_ctx)
2882
+ else:
2883
+ return Bootstrapper_state_selected_env_dir_rel_path_inited_not_func_boot_env(self.env_ctx)
2884
+
2885
+
2699
2886
  # noinspection PyPep8Naming
2700
2887
  @trivial_factory
2701
2888
  class Bootstrapper_state_local_conf_symlink_abs_path_inited(AbstractCachingStateNode[str]):
2702
2889
 
2703
2890
  _parent_states = staticmethod(
2704
2891
  lambda: [
2705
- EnvState.state_input_sub_command_arg_loaded.name,
2706
2892
  EnvState.state_ref_root_dir_abs_path_inited.name,
2707
2893
  EnvState.state_client_conf_file_data_loaded.name,
2708
2894
  EnvState.state_selected_env_dir_rel_path_inited.name,
@@ -2712,8 +2898,6 @@ class Bootstrapper_state_local_conf_symlink_abs_path_inited(AbstractCachingState
2712
2898
 
2713
2899
  def _eval_state_once(self) -> ValueType:
2714
2900
 
2715
- state_input_sub_command_arg_loaded: SubCommand = self.eval_parent_state(EnvState.state_input_sub_command_arg_loaded.name)
2716
-
2717
2901
  state_ref_root_dir_abs_path_inited: str = self.eval_parent_state(EnvState.state_ref_root_dir_abs_path_inited.name)
2718
2902
 
2719
2903
  state_selected_env_dir_rel_path_inited: str | None = self.eval_parent_state(EnvState.state_selected_env_dir_rel_path_inited.name)
@@ -2743,7 +2927,10 @@ class Bootstrapper_state_local_conf_symlink_abs_path_inited(AbstractCachingState
2743
2927
  if os.path.exists(state_local_conf_symlink_abs_path_inited):
2744
2928
  if os.path.islink(state_local_conf_symlink_abs_path_inited):
2745
2929
  if os.path.isdir(state_local_conf_symlink_abs_path_inited):
2746
- if state_input_sub_command_arg_loaded == SubCommand.command_start:
2930
+ if self.env_ctx.graph_coordinates.entry_func in [
2931
+ EntryFunc.func_start_app,
2932
+ EntryFunc.func_call_lib,
2933
+ ]:
2747
2934
  # Nothing to do:
2748
2935
  pass
2749
2936
  else:
@@ -2803,8 +2990,6 @@ class Bootstrapper_state_env_conf_file_data_loaded(AbstractCachingStateNode[dict
2803
2990
 
2804
2991
  _parent_states = staticmethod(
2805
2992
  lambda: [
2806
- EnvState.state_input_stderr_log_level_eval_finalized.name,
2807
- EnvState.state_input_sub_command_arg_loaded.name,
2808
2993
  EnvState.state_local_conf_file_abs_path_inited.name,
2809
2994
  ]
2810
2995
  )
@@ -3119,6 +3304,25 @@ class Bootstrapper_state_venv_driver_inited(AbstractOverriddenFieldCachingStateN
3119
3304
  return state_venv_driver_inited
3120
3305
 
3121
3306
 
3307
+ # noinspection PyPep8Naming
3308
+ @trivial_factory
3309
+ class Bootstrapper_state_version_constraints_file_basename_inited(AbstractOverriddenFieldCachingStateNode[str]):
3310
+
3311
+ _parent_states = staticmethod(
3312
+ lambda: [
3313
+ EnvState.state_client_conf_file_data_loaded.name,
3314
+ EnvState.state_env_conf_file_data_loaded.name,
3315
+ ]
3316
+ )
3317
+ _state_name = staticmethod(lambda: EnvState.state_version_constraints_file_basename_inited.name)
3318
+
3319
+ def _eval_state_once(self) -> ValueType:
3320
+ return self._get_overridden_value_or_default(
3321
+ ConfField.field_version_constraints_file_basename.value,
3322
+ ConfConstEnv.default_version_constraints_file_basename,
3323
+ )
3324
+
3325
+
3122
3326
  # noinspection PyPep8Naming
3123
3327
  @trivial_factory
3124
3328
  class Bootstrapper_state_project_descriptors_inited(AbstractOverriddenFieldCachingStateNode[list]):
@@ -3203,6 +3407,7 @@ class Bootstrapper_state_derived_conf_data_loaded(AbstractCachingStateNode[dict]
3203
3407
  EnvState.state_local_tmp_dir_abs_path_inited.name,
3204
3408
  EnvState.state_local_cache_dir_abs_path_inited.name,
3205
3409
  EnvState.state_venv_driver_inited.name,
3410
+ EnvState.state_version_constraints_file_basename_inited.name,
3206
3411
  EnvState.state_project_descriptors_inited.name,
3207
3412
  ]
3208
3413
 
@@ -3271,7 +3476,7 @@ class Bootstrapper_state_default_file_log_handler_configured(AbstractCachingStat
3271
3476
  _parent_states = staticmethod(
3272
3477
  lambda: [
3273
3478
  EnvState.state_args_parsed.name,
3274
- EnvState.state_input_stderr_log_level_eval_finalized.name,
3479
+ EnvState.state_input_stderr_log_level_handler_configured.name,
3275
3480
  EnvState.state_input_start_id_var_loaded.name,
3276
3481
  EnvState.state_local_log_dir_abs_path_inited.name,
3277
3482
  ]
@@ -3284,7 +3489,7 @@ class Bootstrapper_state_default_file_log_handler_configured(AbstractCachingStat
3284
3489
 
3285
3490
  state_local_log_dir_abs_path_inited: str = self.eval_parent_state(EnvState.state_local_log_dir_abs_path_inited.name)
3286
3491
 
3287
- state_input_stderr_log_level_eval_finalized: int = self.eval_parent_state(EnvState.state_input_stderr_log_level_eval_finalized.name)
3492
+ state_input_stderr_log_level_handler_configured: logging.Handler = self.eval_parent_state(EnvState.state_input_stderr_log_level_handler_configured.name)
3288
3493
 
3289
3494
  script_path = sys.argv[0]
3290
3495
  script_name = os.path.basename(script_path)
@@ -3292,7 +3497,7 @@ class Bootstrapper_state_default_file_log_handler_configured(AbstractCachingStat
3292
3497
  file_handler = _configure_primer_file_log_handler(
3293
3498
  script_name,
3294
3499
  state_input_start_id_var_loaded,
3295
- state_input_stderr_log_level_eval_finalized,
3500
+ state_input_stderr_log_level_handler_configured,
3296
3501
  state_local_log_dir_abs_path_inited,
3297
3502
  )
3298
3503
 
@@ -3330,14 +3535,6 @@ class Bootstrapper_state_stride_py_required_reached_not_command_start(AbstractCa
3330
3535
  if self.env_ctx.has_stride_reached(next_stride=state_stride_py_required_reached):
3331
3536
  return self.env_ctx.set_max_stride(state_stride_py_required_reached)
3332
3537
 
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
3538
  state_input_start_id_var_loaded: str = self.eval_parent_state(EnvState.state_input_start_id_var_loaded.name)
3342
3539
 
3343
3540
  state_proto_code_file_abs_path_inited: str = self.eval_parent_state(EnvState.state_proto_code_file_abs_path_inited.name)
@@ -3425,7 +3622,7 @@ class Factory_state_stride_py_required_reached(NodeFactory[StateStride]):
3425
3622
  @trivial_factory
3426
3623
  class Bootstrapper_state_reboot_triggered(AbstractCachingStateNode[bool]):
3427
3624
  """
3428
- Removes current `venv` dir and `constraints.txt` file (to trigger their re-creation subsequently).
3625
+ Removes current `venv` dir and `version_constraints.txt` file (to trigger their re-creation subsequently).
3429
3626
  """
3430
3627
 
3431
3628
  _parent_states = staticmethod(
@@ -3437,6 +3634,7 @@ class Bootstrapper_state_reboot_triggered(AbstractCachingStateNode[bool]):
3437
3634
  EnvState.state_local_conf_symlink_abs_path_inited.name,
3438
3635
  EnvState.state_local_venv_dir_abs_path_inited.name,
3439
3636
  EnvState.state_local_tmp_dir_abs_path_inited.name,
3637
+ EnvState.state_version_constraints_file_basename_inited.name,
3440
3638
  EnvState.state_stride_py_required_reached.name,
3441
3639
  ]
3442
3640
  )
@@ -3444,8 +3642,6 @@ class Bootstrapper_state_reboot_triggered(AbstractCachingStateNode[bool]):
3444
3642
 
3445
3643
  def _eval_state_once(self) -> ValueType:
3446
3644
 
3447
- state_args_parsed: argparse.Namespace = self.eval_parent_state(EnvState.state_args_parsed.name)
3448
-
3449
3645
  state_input_sub_command_arg_loaded: SubCommand = self.eval_parent_state(EnvState.state_input_sub_command_arg_loaded.name)
3450
3646
 
3451
3647
  if state_input_sub_command_arg_loaded == SubCommand.command_start:
@@ -3463,8 +3659,6 @@ class Bootstrapper_state_reboot_triggered(AbstractCachingStateNode[bool]):
3463
3659
  state_stride_py_required_reached: StateStride = self.eval_parent_state(EnvState.state_stride_py_required_reached.name)
3464
3660
  assert self.env_ctx.get_stride().value >= StateStride.stride_py_required.value
3465
3661
 
3466
- state_proto_code_file_abs_path_inited: str = self.eval_parent_state(EnvState.state_proto_code_file_abs_path_inited.name)
3467
-
3468
3662
  # Reboot can only happen outside `venv` (to delete it):
3469
3663
  if not (reboot_env and state_stride_py_required_reached == StateStride.stride_py_required):
3470
3664
  return False
@@ -3489,9 +3683,10 @@ class Bootstrapper_state_reboot_triggered(AbstractCachingStateNode[bool]):
3489
3683
  )
3490
3684
 
3491
3685
  state_local_conf_symlink_abs_path_inited = self.eval_parent_state(EnvState.state_local_conf_symlink_abs_path_inited.name)
3686
+ state_version_constraints_file_basename_inited: str = self.eval_parent_state(EnvState.state_version_constraints_file_basename_inited.name)
3492
3687
  constraints_txt_path = os.path.join(
3493
3688
  state_local_conf_symlink_abs_path_inited,
3494
- ConfConstEnv.constraints_txt_basename,
3689
+ state_version_constraints_file_basename_inited,
3495
3690
  )
3496
3691
  if os.path.exists(constraints_txt_path):
3497
3692
  logger.info(f"removing version constraints file [{constraints_txt_path}]")
@@ -3597,8 +3792,6 @@ class Bootstrapper_state_stride_py_venv_reached(AbstractCachingStateNode[StateSt
3597
3792
 
3598
3793
  state_input_start_id_var_loaded: str = self.eval_parent_state(EnvState.state_input_start_id_var_loaded.name)
3599
3794
 
3600
- state_reboot_triggered: bool = self.eval_parent_state(EnvState.state_reboot_triggered.name)
3601
-
3602
3795
  state_proto_code_file_abs_path_inited: str = self.eval_parent_state(EnvState.state_proto_code_file_abs_path_inited.name)
3603
3796
 
3604
3797
  state_selected_python_file_abs_path_inited: str = self.eval_parent_state(EnvState.state_selected_python_file_abs_path_inited.name)
@@ -3666,6 +3859,7 @@ class Bootstrapper_state_protoprimer_package_installed(AbstractCachingStateNode[
3666
3859
  EnvState.state_input_sub_command_arg_loaded.name,
3667
3860
  EnvState.state_ref_root_dir_abs_path_inited.name,
3668
3861
  EnvState.state_local_conf_symlink_abs_path_inited.name,
3862
+ EnvState.state_version_constraints_file_basename_inited.name,
3669
3863
  EnvState.state_project_descriptors_inited.name,
3670
3864
  EnvState.state_install_specs_inited.name,
3671
3865
  EnvState.state_venv_driver_prepared.name,
@@ -3701,6 +3895,8 @@ class Bootstrapper_state_protoprimer_package_installed(AbstractCachingStateNode[
3701
3895
 
3702
3896
  state_venv_driver_prepared: VenvDriverBase = self.eval_parent_state(EnvState.state_venv_driver_prepared.name)
3703
3897
 
3898
+ state_version_constraints_file_basename_inited: str = self.eval_parent_state(EnvState.state_version_constraints_file_basename_inited.name)
3899
+
3704
3900
  reboot_env: bool = state_args_parsed.sub_command == SubCommand.command_reboot.value
3705
3901
 
3706
3902
  do_install: bool = (
@@ -3714,7 +3910,7 @@ class Bootstrapper_state_protoprimer_package_installed(AbstractCachingStateNode[
3714
3910
 
3715
3911
  constraints_txt_path = os.path.join(
3716
3912
  state_local_conf_symlink_abs_path_inited,
3717
- ConfConstEnv.constraints_txt_basename,
3913
+ state_version_constraints_file_basename_inited,
3718
3914
  )
3719
3915
  if not os.path.exists(constraints_txt_path):
3720
3916
  logger.info(f"creating empty constraints file [{constraints_txt_path}]")
@@ -3806,6 +4002,7 @@ class Bootstrapper_state_version_constraints_generated(AbstractCachingStateNode[
3806
4002
  lambda: [
3807
4003
  EnvState.state_input_sub_command_arg_loaded.name,
3808
4004
  EnvState.state_local_conf_symlink_abs_path_inited.name,
4005
+ EnvState.state_version_constraints_file_basename_inited.name,
3809
4006
  EnvState.state_venv_driver_prepared.name,
3810
4007
  EnvState.state_protoprimer_package_installed.name,
3811
4008
  ]
@@ -3818,7 +4015,7 @@ class Bootstrapper_state_version_constraints_generated(AbstractCachingStateNode[
3818
4015
 
3819
4016
  if state_input_sub_command_arg_loaded == SubCommand.command_start:
3820
4017
  # The only reason for `EnvState.state_version_constraints_generated`
3821
- # is to re-generate the `constraints.txt` file based on `venv`.
4018
+ # is to re-generate the `version_constraints.txt` file based on `venv`.
3822
4019
  # Skip it as `venv` is supposed to be ready in `SubCommand.command_start`:
3823
4020
  return False
3824
4021
 
@@ -3831,11 +4028,13 @@ class Bootstrapper_state_version_constraints_generated(AbstractCachingStateNode[
3831
4028
 
3832
4029
  state_venv_driver_prepared: VenvDriverBase = self.eval_parent_state(EnvState.state_venv_driver_prepared.name)
3833
4030
 
4031
+ state_version_constraints_file_basename_inited: str = self.eval_parent_state(EnvState.state_version_constraints_file_basename_inited.name)
4032
+
3834
4033
  state_venv_driver_prepared.pin_versions(
3835
4034
  get_path_to_curr_python(),
3836
4035
  os.path.join(
3837
4036
  state_local_conf_symlink_abs_path_inited,
3838
- ConfConstEnv.constraints_txt_basename,
4037
+ state_version_constraints_file_basename_inited,
3839
4038
  ),
3840
4039
  )
3841
4040
 
@@ -3875,8 +4074,6 @@ class Bootstrapper_state_stride_deps_updated_reached(AbstractCachingStateNode[St
3875
4074
 
3876
4075
  state_proto_code_file_abs_path_inited: str = self.eval_parent_state(EnvState.state_proto_code_file_abs_path_inited.name)
3877
4076
 
3878
- state_version_constraints_generated: bool = self.eval_parent_state(EnvState.state_version_constraints_generated.name)
3879
-
3880
4077
  state_local_venv_dir_abs_path_inited: str = self.eval_parent_state(EnvState.state_local_venv_dir_abs_path_inited.name)
3881
4078
 
3882
4079
  venv_path_to_python: str = os.path.join(
@@ -3915,7 +4112,6 @@ class Bootstrapper_state_proto_code_updated(AbstractCachingStateNode[bool]):
3915
4112
 
3916
4113
  def _eval_state_once(self) -> ValueType:
3917
4114
 
3918
- state_stride_deps_updated_reached: StateStride = self.eval_parent_state(EnvState.state_stride_deps_updated_reached.name)
3919
4115
  assert self.env_ctx.get_stride().value >= StateStride.stride_deps_updated.value
3920
4116
 
3921
4117
  if self.env_ctx.get_stride().value != StateStride.stride_deps_updated.value:
@@ -4002,15 +4198,11 @@ class Bootstrapper_state_stride_src_updated_reached(AbstractCachingStateNode[Sta
4002
4198
 
4003
4199
  state_stride_src_updated_reached: StateStride = StateStride.stride_src_updated
4004
4200
 
4005
- state_input_sub_command_arg_loaded: SubCommand = self.eval_parent_state(EnvState.state_input_sub_command_arg_loaded.name)
4006
-
4007
4201
  if self.env_ctx.has_stride_reached(next_stride=state_stride_src_updated_reached):
4008
4202
  return self.env_ctx.set_max_stride(state_stride_src_updated_reached)
4009
4203
 
4010
4204
  state_proto_code_file_abs_path_inited: str = self.eval_parent_state(EnvState.state_proto_code_file_abs_path_inited.name)
4011
4205
 
4012
- state_proto_code_updated: bool = self.eval_parent_state(EnvState.state_proto_code_updated.name)
4013
-
4014
4206
  state_local_venv_dir_abs_path_inited: str = self.eval_parent_state(EnvState.state_local_venv_dir_abs_path_inited.name)
4015
4207
 
4016
4208
  venv_path_to_python: str = os.path.join(
@@ -4101,16 +4293,24 @@ class EnvState(enum.Enum):
4101
4293
 
4102
4294
  state_default_stderr_log_handler_configured = Bootstrapper_state_default_stderr_log_handler_configured
4103
4295
 
4104
- state_args_parsed = Bootstrapper_state_args_parsed
4296
+ state_args_parsed = Factory_state_args_parsed
4105
4297
 
4106
4298
  state_input_stderr_log_level_eval_finalized = Bootstrapper_state_input_stderr_log_level_eval_finalized
4107
4299
 
4300
+ state_input_stderr_log_level_handler_configured = Bootstrapper_state_input_stderr_log_level_handler_configured
4301
+
4108
4302
  state_input_sub_command_arg_loaded = Bootstrapper_state_input_sub_command_arg_loaded
4109
4303
 
4110
- state_input_final_state_eval_finalized = Bootstrapper_state_input_final_state_eval_finalized
4304
+ state_input_final_state_eval_finalized = Factory_state_input_final_state_eval_finalized
4305
+
4306
+ state_func_boot_env_executed = Bootstrapper_state_func_boot_env_executed
4307
+
4308
+ state_func_start_app_executed = Bootstrapper_state_func_start_app_executed
4309
+
4310
+ state_func_call_lib_executed = Bootstrapper_state_func_call_lib_executed
4111
4311
 
4112
4312
  # Special case: triggers everything:
4113
- state_sub_command_executed = Bootstrapper_state_sub_command_executed
4313
+ state_everything_executed = Factory_state_everything_executed
4114
4314
 
4115
4315
  state_input_start_id_var_loaded = Bootstrapper_state_input_start_id_var_loaded
4116
4316
 
@@ -4135,7 +4335,7 @@ class EnvState(enum.Enum):
4135
4335
  # `ConfLeap.leap_client`:
4136
4336
  state_client_conf_file_data_loaded = Bootstrapper_state_client_conf_file_data_loaded
4137
4337
 
4138
- state_selected_env_dir_rel_path_inited = Bootstrapper_state_selected_env_dir_rel_path_inited
4338
+ state_selected_env_dir_rel_path_inited = Factory_state_selected_env_dir_rel_path_inited
4139
4339
 
4140
4340
  state_local_conf_symlink_abs_path_inited = Bootstrapper_state_local_conf_symlink_abs_path_inited
4141
4341
 
@@ -4165,6 +4365,8 @@ class EnvState(enum.Enum):
4165
4365
 
4166
4366
  state_venv_driver_inited = Bootstrapper_state_venv_driver_inited
4167
4367
 
4368
+ state_version_constraints_file_basename_inited = Bootstrapper_state_version_constraints_file_basename_inited
4369
+
4168
4370
  state_project_descriptors_inited = Bootstrapper_state_project_descriptors_inited
4169
4371
 
4170
4372
  state_install_specs_inited = Bootstrapper_state_install_specs_inited
@@ -4209,11 +4411,8 @@ class TargetState(enum.Enum):
4209
4411
  Special `EnvState`-s.
4210
4412
  """
4211
4413
 
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
4414
+ # A special state that triggers execution of everything else:
4415
+ target_everything_executed = EnvState.state_everything_executed
4217
4416
 
4218
4417
  # FT_00_22_19_59.derived_config.md:
4219
4418
  target_derived_config_loaded = EnvState.state_derived_conf_data_loaded
@@ -4329,14 +4528,13 @@ class MutableValue(Generic[ValueType]):
4329
4528
 
4330
4529
  def set_curr_value(
4331
4530
  self,
4332
- state_node: StateNode | None,
4531
+ state_node: StateNode,
4333
4532
  curr_value: ValueType,
4334
4533
  ) -> None:
4335
4534
  # TODO: Shell we also ensure that the `StateNode` using that `MutableValue` has necessary dependencies on write?
4336
4535
 
4337
4536
  if self.curr_value is None:
4338
4537
  raise AssertionError(f"`{MutableValue.__name__}` [{self.state_name}] cannot be set as it is not initialized yet.")
4339
- state_name: str | None = None
4340
4538
  self.curr_value = curr_value
4341
4539
  logger.debug(f"`{self.__class__.__name__}` [{self.state_name}] `curr_value` after set [{self.curr_value}] in [{state_node.get_state_name()}]")
4342
4540
 
@@ -4614,40 +4812,53 @@ class _PrimerStderrLogFormatter(DefaultStderrLogFormatter):
4614
4812
  }
4615
4813
 
4616
4814
 
4815
+ def _reconfigure_log_handler(
4816
+ formatter_class: type,
4817
+ new_formatter: logging.Formatter,
4818
+ log_level: int,
4819
+ ) -> logging.Handler | None:
4820
+ for log_handler in logger.handlers:
4821
+ if isinstance(log_handler.formatter, formatter_class):
4822
+ # This covers both `_Primer*` (subclass) and `Default*` formatter variants.
4823
+ log_handler.setFormatter(new_formatter)
4824
+ log_handler.setLevel(log_level)
4825
+ # Remove `protoprimer`-specific filter if present:
4826
+ for handler_filter in list(log_handler.filters):
4827
+ if isinstance(handler_filter, StateStrideFilter):
4828
+ log_handler.removeFilter(handler_filter)
4829
+ return log_handler
4830
+ return None
4831
+
4832
+
4617
4833
  def reconfigure_stderr_log_handler(log_level: int = logging.WARNING) -> logging.Handler | None:
4618
4834
  """
4619
4835
  UC_81_50_97_17.reuse_logger.md
4620
4836
  """
4621
-
4622
4837
  logger.setLevel(logging.NOTSET)
4838
+ return _reconfigure_log_handler(
4839
+ formatter_class=DefaultStderrLogFormatter,
4840
+ new_formatter=DefaultStderrLogFormatter(log_level),
4841
+ log_level=log_level,
4842
+ )
4623
4843
 
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
4844
 
4845
+ def _find_existing_log_handler(
4846
+ handler_class: type[logging.StreamHandler],
4847
+ formatter_class: type,
4848
+ ) -> logging.StreamHandler | None:
4849
+ """
4850
+ Prevent duplicate handler (when `os.execv*` calls restart `main` again in tests).
4851
+ """
4852
+ if os.environ.get(EnvVar.var_PROTOPRIMER_MOCKED_RESTART.value, None) is None:
4853
+ return None
4854
+ log_handler: logging.Handler
4855
+ for log_handler in logger.handlers:
4856
+ if isinstance(log_handler, handler_class):
4857
+ if isinstance(log_handler.formatter, formatter_class):
4858
+ return log_handler
4639
4859
  return None
4640
4860
 
4641
4861
 
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
4862
  def _configure_primer_stderr_log_handler(state_input_stderr_log_level_var_loaded: int) -> logging.Handler:
4652
4863
  """
4653
4864
  Implements for `stderr` log: FT_38_73_38_52.log_verbosity.md
@@ -4656,23 +4867,16 @@ def _configure_primer_stderr_log_handler(state_input_stderr_log_level_var_loaded
4656
4867
  # Log everything (the filters are supposed to be set on output handlers instead):
4657
4868
  logger.setLevel(logging.NOTSET)
4658
4869
 
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
4870
+ stderr_handler: logging.StreamHandler | None = _find_existing_log_handler(
4871
+ logging.StreamHandler,
4872
+ _PrimerStderrLogFormatter,
4873
+ )
4669
4874
 
4670
4875
  if stderr_handler is None:
4671
- stderr_handler: logging.Handler = handler_class(sys.stderr)
4672
-
4876
+ stderr_handler = logging.StreamHandler(sys.stderr)
4877
+ assert stderr_handler is not None
4673
4878
  stderr_handler.addFilter(StateStrideFilter())
4674
4879
  stderr_handler.setFormatter(_PrimerStderrLogFormatter(state_input_stderr_log_level_var_loaded))
4675
-
4676
4880
  logger.addHandler(stderr_handler)
4677
4881
 
4678
4882
  stderr_handler.setLevel(state_input_stderr_log_level_var_loaded)
@@ -4683,41 +4887,17 @@ def reconfigure_file_log_handler(log_level: int = logging.INFO) -> logging.Handl
4683
4887
  """
4684
4888
  UC_81_50_97_17.reuse_logger.md
4685
4889
  """
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
4890
+ return _reconfigure_log_handler(
4891
+ formatter_class=DefaultFileLogFormatter,
4892
+ new_formatter=DefaultFileLogFormatter(),
4893
+ log_level=log_level,
4894
+ )
4715
4895
 
4716
4896
 
4717
4897
  def _configure_primer_file_log_handler(
4718
4898
  script_name: str,
4719
4899
  state_input_start_id_var_loaded: str,
4720
- state_input_stderr_log_level_eval_finalized: int,
4900
+ state_input_stderr_log_level_handler_configured: logging.Handler,
4721
4901
  state_local_log_dir_abs_path_inited: str,
4722
4902
  ) -> logging.Handler:
4723
4903
  """
@@ -4733,33 +4913,37 @@ def _configure_primer_file_log_handler(
4733
4913
  # TODO: Configure MAX file log level in the config file (NOTE: the higher the level the fewer the log entries):
4734
4914
  file_log_level: int = logging.INFO
4735
4915
  # 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
4916
+ if state_input_stderr_log_level_handler_configured.level < file_log_level:
4917
+ file_log_level = state_input_stderr_log_level_handler_configured.level
4738
4918
 
4739
- os.makedirs(
4740
- state_local_log_dir_abs_path_inited,
4741
- exist_ok=True,
4919
+ file_handler: logging.StreamHandler | None = _find_existing_log_handler(
4920
+ logging.FileHandler,
4921
+ _PrimerFileLogFormatter,
4742
4922
  )
4743
4923
 
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"""
4924
+ if file_handler is None:
4925
+ os.makedirs(
4926
+ state_local_log_dir_abs_path_inited,
4927
+ exist_ok=True,
4928
+ )
4929
+
4930
+ if not os.path.exists(log_file_abs_path):
4931
+ # Explain missing logs to avoid confusion:
4932
+ write_text_file(
4933
+ log_file_abs_path,
4934
+ f"""
4749
4935
  {ConfConstGeneral.log_section_delimiter} file log starts at [{StateStride.stride_py_arbitrary.name}] after its config is resolved {ConfConstGeneral.log_section_delimiter}
4750
4936
 
4751
4937
  """,
4752
- )
4753
-
4754
- file_handler: logging.Handler = logging.FileHandler(log_file_abs_path)
4755
- file_handler.addFilter(StateStrideFilter())
4938
+ )
4756
4939
 
4757
- file_formatter = _PrimerFileLogFormatter()
4940
+ file_handler = logging.FileHandler(log_file_abs_path)
4941
+ assert file_handler is not None
4942
+ file_handler.addFilter(StateStrideFilter())
4943
+ file_handler.setFormatter(_PrimerFileLogFormatter())
4944
+ logger.addHandler(file_handler)
4758
4945
 
4759
4946
  file_handler.setLevel(file_log_level)
4760
- file_handler.setFormatter(file_formatter)
4761
-
4762
- logger.addHandler(file_handler)
4763
4947
  return file_handler
4764
4948
 
4765
4949
 
@@ -5197,7 +5381,7 @@ def select_python_file_abs_path(
5197
5381
  try:
5198
5382
  logger.debug(f"trying `python` version of `selected_python_abs_path` [{selected_python_abs_path}]")
5199
5383
  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}]")
5384
+ logger.debug(f"`python` version of `selected_python_abs_path` [{selected_python_abs_path}] is [{python_version}]")
5201
5385
  except (subprocess.CalledProcessError, FileNotFoundError):
5202
5386
  logger.warning(f"`python` in `selected_python_abs_path` [{selected_python_abs_path}] failed without returning its version")
5203
5387
  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.1.dev0
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.1.dev0"
9
9
 
10
10
  authors = [
11
11
  { name = "uvsmtid", email = "uvsmtid@gmail.com" },