Coverage for sources/ictruck/vehicles.py: 100%

234 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-07-05 05:21 +0000

1# vim: set filetype=python fileencoding=utf-8: 

2# -*- coding: utf-8 -*- 

3 

4#============================================================================# 

5# # 

6# Licensed under the Apache License, Version 2.0 (the "License"); # 

7# you may not use this file except in compliance with the License. # 

8# You may obtain a copy of the License at # 

9# # 

10# http://www.apache.org/licenses/LICENSE-2.0 # 

11# # 

12# Unless required by applicable law or agreed to in writing, software # 

13# distributed under the License is distributed on an "AS IS" BASIS, # 

14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 

15# See the License for the specific language governing permissions and # 

16# limitations under the License. # 

17# # 

18#============================================================================# 

19 

20 

21''' Vehicles which vend flavors of Icecream debugger. ''' 

22 

23 

24 

25import icecream as _icecream 

26 

27from . import __ 

28from . import configuration as _cfg 

29from . import exceptions as _exceptions 

30from . import printers as _printers 

31 

32 

33# if __.typx.TYPE_CHECKING: # pragma: no cover 

34# import _typeshed 

35 

36 

37_installer_lock: __.threads.Lock = __.threads.Lock( ) 

38_registrar_lock: __.threads.Lock = __.threads.Lock( ) 

39_self_modulecfg: _cfg.ModuleConfiguration = _cfg.ModuleConfiguration( 

40 flavors = __.immut.Dictionary( 

41 note = _cfg.FlavorConfiguration( prefix_emitter = 'NOTE| ' ), 

42 error = _cfg.FlavorConfiguration( prefix_emitter = 'ERROR| ' ) ) ) 

43_validate_arguments = ( 

44 __.validate_arguments( 

45 globalvars = globals( ), 

46 errorclass = _exceptions.ArgumentClassInvalidity ) ) 

47 

48 

49class ModulesConfigurationsRegistry( 

50 __.accret.Dictionary[ str, _cfg.ModuleConfiguration ] 

51): 

52 ''' Accretive dictionary specifically for module registrations. ''' 

53 

54 def __init__( 

55 self, 

56 *iterables: __.DictionaryPositionalArgument[ 

57 str, _cfg.ModuleConfiguration ], 

58 **entries: __.DictionaryNominativeArgument[ 

59 _cfg.ModuleConfiguration ], 

60 ): 

61 super( ).__init__( { __.package_name: _self_modulecfg } ) 

62 self.update( *iterables, **entries ) 

63 

64 

65class Omniflavor( __.enum.Enum ): 

66 ''' Singleton to match any flavor. ''' 

67 

68 Instance = __.enum.auto( ) 

69 

70 

71ActiveFlavors: __.typx.TypeAlias = Omniflavor | frozenset[ _cfg.Flavor ] 

72ActiveFlavorsLiberal: __.typx.TypeAlias = __.typx.Union[ 

73 Omniflavor, 

74 __.cabc.Sequence[ _cfg.Flavor ], 

75 __.cabc.Set[ _cfg.Flavor ], 

76] 

77ActiveFlavorsRegistry: __.typx.TypeAlias = ( 

78 __.immut.Dictionary[ str | None, ActiveFlavors ] ) 

79ActiveFlavorsRegistryLiberal: __.typx.TypeAlias = ( 

80 __.cabc.Mapping[ str | None, ActiveFlavorsLiberal ] ) 

81ModulesConfigurationsRegistryLiberal: __.typx.TypeAlias = ( 

82 __.cabc.Mapping[ str, _cfg.ModuleConfiguration ] ) 

83ReportersRegistry: __.typx.TypeAlias = ( 

84 __.accret.Dictionary[ 

85 tuple[ str, _cfg.Flavor ], _icecream.IceCreamDebugger ] ) 

86TraceLevelsRegistry: __.typx.TypeAlias = ( 

87 __.immut.Dictionary[ str | None, int ] ) 

88TraceLevelsRegistryLiberal: __.typx.TypeAlias = ( 

89 __.cabc.Mapping[ str | None, int ] ) 

90 

91 

92builtins_alias_default: __.typx.Annotated[ 

93 str, 

94 __.typx.Doc( ''' Default alias for global truck in builtins module. ''' ), 

95] = 'ictr' 

96modulecfgs: __.typx.Annotated[ 

97 ModulesConfigurationsRegistry, 

98 __.typx.Doc( ''' Global registry of module configurations. ''' ), 

99] = ModulesConfigurationsRegistry( ) 

100omniflavor: __.typx.Annotated[ 

101 Omniflavor, __.typx.Doc( ''' Matches any flavor. ''' ) 

102] = Omniflavor.Instance 

103 

104 

105class Truck( __.immut.DataclassObject ): 

106 ''' Vends flavors of Icecream debugger. ''' 

107 

108 active_flavors: __.typx.Annotated[ 

109 ActiveFlavorsRegistry, 

110 __.typx.Doc( 

111 ''' Mapping of module names to active flavor sets. 

112 

113 Key ``None`` applies globally. Module-specific entries 

114 override globals for that module. 

115 ''' ), 

116 ] = __.dcls.field( default_factory = ActiveFlavorsRegistry ) 

117 generalcfg: __.typx.Annotated[ 

118 _cfg.VehicleConfiguration, 

119 __.typx.Doc( 

120 ''' General configuration. 

121 

122 Top of configuration inheritance hierarchy. 

123 Default is suitable for application use. 

124 ''' ), 

125 ] = __.dcls.field( default_factory = _cfg.VehicleConfiguration ) 

126 modulecfgs: __.typx.Annotated[ 

127 ModulesConfigurationsRegistry, 

128 __.typx.Doc( 

129 ''' Registry of per-module configurations. 

130 

131 Modules inherit configuration from their parent packages. 

132 Top-level packages inherit from general instance 

133 configruration. 

134 ''' ), 

135 ] = __.dcls.field( default_factory = lambda: modulecfgs ) 

136 printer_factory: __.typx.Annotated[ 

137 _printers.PrinterFactoryUnion, 

138 __.typx.Doc( 

139 ''' Factory which produces callables to output text somewhere. 

140 

141 May also be writable text stream. 

142 Factories take two arguments, module name and flavor, and 

143 return a callable which takes one argument, the string 

144 produced by a formatter. 

145 ''' ), 

146 ] = __.funct.partial( _printers.produce_simple_printer, __.sys.stderr ) 

147 trace_levels: __.typx.Annotated[ 

148 TraceLevelsRegistry, 

149 __.typx.Doc( 

150 ''' Mapping of module names to maximum trace depths. 

151 

152 Key ``None`` applies globally. Module-specific entries 

153 override globals for that module. 

154 ''' ), 

155 ] = __.dcls.field( 

156 default_factory = lambda: __.immut.Dictionary( { None: -1 } ) ) 

157 _debuggers: __.typx.Annotated[ 

158 ReportersRegistry, 

159 __.typx.Doc( 

160 ''' Cache of debugger instances by module and flavor. ''' ), 

161 ] = __.dcls.field( default_factory = ReportersRegistry ) 

162 _debuggers_lock: __.typx.Annotated[ 

163 __.threads.Lock, 

164 __.typx.Doc( ''' Access lock for cache of debugger instances. ''' ), 

165 ] = __.dcls.field( default_factory = __.threads.Lock ) 

166 

167 @_validate_arguments 

168 def __call__( 

169 self, 

170 flavor: _cfg.Flavor, *, 

171 module_name: __.Absential[ str ] = __.absent, 

172 ) -> _icecream.IceCreamDebugger: 

173 ''' Vends flavor of Icecream debugger. ''' 

174 mname = ( 

175 _discover_invoker_module_name( ) if __.is_absent( module_name ) 

176 else module_name ) 

177 cache_index = ( mname, flavor ) 

178 if cache_index in self._debuggers: 

179 with self._debuggers_lock: 

180 return self._debuggers[ cache_index ] 

181 configuration = _produce_ic_configuration( self, mname, flavor ) 

182 control = _cfg.FormatterControl( ) 

183 initargs = _calculate_ic_initargs( 

184 self, configuration, control, mname, flavor ) 

185 debugger = _icecream.IceCreamDebugger( **initargs ) 

186 if isinstance( flavor, int ): 

187 trace_level = ( 

188 _calculate_effective_trace_level( self.trace_levels, mname) ) 

189 debugger.enabled = flavor <= trace_level 

190 elif isinstance( flavor, str ): # pragma: no branch 

191 active_flavors = ( 

192 _calculate_effective_flavors( self.active_flavors, mname ) ) 

193 debugger.enabled = ( 

194 isinstance( active_flavors, Omniflavor ) 

195 or flavor in active_flavors ) 

196 with self._debuggers_lock: 

197 self._debuggers[ cache_index ] = debugger 

198 return debugger 

199 

200 @_validate_arguments 

201 def install( self, alias: str = builtins_alias_default ) -> __.typx.Self: 

202 ''' Installs truck into builtins with provided alias. 

203 

204 Replaces an existing truck. Preserves global module configurations. 

205 

206 Library developers should call :py:func:`register_module` instead. 

207 ''' 

208 import builtins 

209 with _installer_lock: 

210 truck_o = getattr( builtins, alias, None ) 

211 if isinstance( truck_o, Truck ): 

212 self( 'note', module_name = __name__ )( 

213 'Installed truck is being replaced.' ) 

214 setattr( builtins, alias, self ) 

215 else: 

216 __.install_builtin_safely( 

217 alias, self, _exceptions.AttributeNondisplacement ) 

218 return self 

219 

220 @_validate_arguments 

221 def register_module( 

222 self, 

223 name: __.Absential[ str ] = __.absent, 

224 configuration: __.Absential[ _cfg.ModuleConfiguration ] = __.absent, 

225 ) -> __.typx.Self: 

226 ''' Registers configuration for module. 

227 

228 If no module or package name is given, then the current module is 

229 inferred. 

230 

231 If no configuration is provided, then a default is generated. 

232 ''' 

233 if __.is_absent( name ): 

234 name = _discover_invoker_module_name( ) 

235 if __.is_absent( configuration ): 

236 configuration = _cfg.ModuleConfiguration( ) 

237 with _registrar_lock: 

238 self.modulecfgs[ name ] = configuration 

239 return self 

240 

241InstallAliasArgument: __.typx.TypeAlias = __.typx.Annotated[ 

242 str, 

243 __.typx.Doc( 

244 ''' Alias under which the truck is installed in builtins. ''' ), 

245] 

246ProduceTruckActiveFlavorsArgument: __.typx.TypeAlias = __.typx.Annotated[ 

247 __.Absential[ ActiveFlavorsLiberal | ActiveFlavorsRegistryLiberal ], 

248 __.typx.Doc( 

249 ''' Flavors to activate. 

250 

251 Can be collection, which applies globally across all registered 

252 modules. Or, can be mapping of module names to sets. 

253 

254 Module-specific entries merge with global entries. 

255 ''' ), 

256] 

257ProduceTruckEvnActiveFlavorsArgument: __.typx.TypeAlias = __.typx.Annotated[ 

258 __.Absential[ __.typx.Optional[ str ] ], 

259 __.typx.Doc( 

260 ''' Name of environment variable for active flavors or ``None``. 

261 

262 If absent, then a default environment variable name is used. 

263 

264 If ``None``, then active flavors are not parsed from the process 

265 environment. 

266 

267 If active flavors are supplied directly to a function, 

268 which also accepts this argument, then active flavors are not 

269 parsed from the process environment. 

270 ''' ), 

271] 

272ProduceTruckEvnTraceLevelsArgument: __.typx.TypeAlias = __.typx.Annotated[ 

273 __.Absential[ __.typx.Optional[ str ] ], 

274 __.typx.Doc( 

275 ''' Name of environment variable for trace levels or ``None``. 

276 

277 If absent, then a default environment variable name is used. 

278 

279 If ``None``, then trace levels are not parsed from the process 

280 environment. 

281 

282 If trace levels are supplied directly to a function, 

283 which also accepts this argument, then trace levels are not 

284 parsed from the process environment. 

285 ''' ), 

286] 

287ProduceTruckFlavorsArgument: __.typx.TypeAlias = __.typx.Annotated[ 

288 __.Absential[ _cfg.FlavorsRegistryLiberal ], 

289 __.typx.Doc( ''' Registry of flavor identifiers to configurations. ''' ), 

290] 

291ProduceTruckGeneralcfgArgument: __.typx.TypeAlias = __.typx.Annotated[ 

292 __.Absential[ _cfg.VehicleConfiguration ], 

293 __.typx.Doc( 

294 ''' General configuration for the truck. 

295 

296 Top of configuration inheritance hierarchy. If absent, 

297 defaults to a suitable configuration for application use. 

298 ''' ), 

299] 

300ProduceTruckModulecfgsArgument: __.typx.TypeAlias = __.typx.Annotated[ 

301 __.Absential[ ModulesConfigurationsRegistryLiberal ], 

302 __.typx.Doc( 

303 ''' Module configurations for the truck. 

304 

305 If absent, defaults to global modules registry. 

306 ''' ), 

307] 

308ProduceTruckPrinterFactoryArgument: __.typx.TypeAlias = __.typx.Annotated[ 

309 __.Absential[ _printers.PrinterFactoryUnion ], 

310 __.typx.Doc( 

311 ''' Factory which produces callables to output text somewhere. 

312 

313 May also be writable text stream. 

314 Factories take two arguments, module name and flavor, and 

315 return a callable which takes one argument, the string 

316 produced by a formatter. 

317 

318 If absent, uses a default. 

319 ''' ), 

320] 

321ProduceTruckTraceLevelsArgument: __.typx.TypeAlias = __.typx.Annotated[ 

322 __.Absential[ int | TraceLevelsRegistryLiberal ], 

323 __.typx.Doc( 

324 ''' Maximum trace depths. 

325 

326 Can be an integer, which applies globally across all registered 

327 modules. Or, can be a mapping of module names to integers. 

328 

329 Module-specific entries override global entries. 

330 ''' ), 

331] 

332RegisterModuleFormatterFactoryArgument: __.typx.TypeAlias = __.typx.Annotated[ 

333 __.Absential[ _cfg.FormatterFactory ], 

334 __.typx.Doc( 

335 ''' Factory which produces formatter callable. 

336 

337 Takes formatter control, module name, and flavor as arguments. 

338 Returns formatter to convert an argument to a string. 

339 ''' ), 

340] 

341RegisterModuleIncludeContextArgument: __.typx.TypeAlias = __.typx.Annotated[ 

342 __.Absential[ bool ], 

343 __.typx.Doc( ''' Include stack frame with output? ''' ), 

344] 

345RegisterModuleNameArgument: __.typx.TypeAlias = __.typx.Annotated[ 

346 __.Absential[ str ], 

347 __.typx.Doc( 

348 ''' Name of the module to register. 

349 

350 If absent, infers the current module name. 

351 ''' ), 

352] 

353RegisterModulePrefixEmitterArgument: __.typx.TypeAlias = __.typx.Annotated[ 

354 __.Absential[ _cfg.PrefixEmitterUnion ], 

355 __.typx.Doc( 

356 ''' String or factory which produces output prefix string. 

357 

358 Factory takes formatter control, module name, and flavor as 

359 arguments. Returns prefix string. 

360 ''' ), 

361] 

362 

363 

364def active_flavors_from_environment( 

365 evname: __.Absential[ str ] = __.absent 

366) -> ActiveFlavorsRegistry: 

367 ''' Extracts active flavors from named environment variable. ''' 

368 active_flavors: ActiveFlavorsRegistryLiberal = { } 

369 name = 'ICTRUCK_ACTIVE_FLAVORS' if __.is_absent( evname ) else evname 

370 value = __.os.getenv( name, '' ) 

371 for part in value.split( '+' ): 

372 if not part: continue 

373 if ':' in part: 

374 mname, flavors = part.split( ':', 1 ) 

375 else: mname, flavors = None, part 

376 match flavors: 

377 case '*': active_flavors[ mname ] = omniflavor 

378 case _: active_flavors[ mname ] = flavors.split( ',' ) 

379 return __.immut.Dictionary( { 

380 mname: 

381 flavors if isinstance( flavors, Omniflavor ) 

382 else frozenset( flavors ) 

383 for mname, flavors in active_flavors.items( ) } ) 

384 

385 

386def trace_levels_from_environment( 

387 evname: __.Absential[ str ] = __.absent 

388) -> TraceLevelsRegistry: 

389 ''' Extracts trace levels from named environment variable. ''' 

390 trace_levels: TraceLevelsRegistryLiberal = { None: -1 } 

391 name = 'ICTRUCK_TRACE_LEVELS' if __.is_absent( evname ) else evname 

392 value = __.os.getenv( name, '' ) 

393 for part in value.split( '+' ): 

394 if not part: continue 

395 if ':' in part: mname, level = part.split( ':', 1 ) 

396 else: mname, level = None, part 

397 if not level.isdigit( ): 

398 __.warnings.warn( 

399 f"Non-integer trace level {level!r} " 

400 f"in environment variable {name!r}." ) 

401 continue 

402 trace_levels[ mname ] = int( level ) 

403 return __.immut.Dictionary( trace_levels ) 

404 

405 

406@_validate_arguments 

407def install( # noqa: PLR0913 

408 alias: InstallAliasArgument = builtins_alias_default, 

409 active_flavors: ProduceTruckActiveFlavorsArgument = __.absent, 

410 generalcfg: ProduceTruckGeneralcfgArgument = __.absent, 

411 printer_factory: ProduceTruckPrinterFactoryArgument = __.absent, 

412 trace_levels: ProduceTruckTraceLevelsArgument = __.absent, 

413 evname_active_flavors: ProduceTruckEvnActiveFlavorsArgument = __.absent, 

414 evname_trace_levels: ProduceTruckEvnTraceLevelsArgument = __.absent, 

415) -> Truck: 

416 ''' Produces truck and installs it into builtins with alias. 

417 

418 Replaces an existing truck, preserving global module configurations. 

419 

420 Library developers should call :py:func:`register_module` instead. 

421 ''' 

422 truck = produce_truck( 

423 active_flavors = active_flavors, 

424 generalcfg = generalcfg, 

425 printer_factory = printer_factory, 

426 trace_levels = trace_levels, 

427 evname_active_flavors = evname_active_flavors, 

428 evname_trace_levels = evname_trace_levels ) 

429 return truck.install( alias = alias ) 

430 

431 

432@_validate_arguments 

433def produce_truck( # noqa: PLR0913 

434 active_flavors: ProduceTruckActiveFlavorsArgument = __.absent, 

435 generalcfg: ProduceTruckGeneralcfgArgument = __.absent, 

436 modulecfgs: ProduceTruckModulecfgsArgument = __.absent, 

437 printer_factory: ProduceTruckPrinterFactoryArgument = __.absent, 

438 trace_levels: ProduceTruckTraceLevelsArgument = __.absent, 

439 evname_active_flavors: ProduceTruckEvnActiveFlavorsArgument = __.absent, 

440 evname_trace_levels: ProduceTruckEvnTraceLevelsArgument = __.absent, 

441) -> Truck: 

442 ''' Produces icecream truck with some shorthand argument values. ''' 

443 # TODO: Deeper validation of active flavors and trace levels. 

444 # TODO: Deeper validation of printer factory. 

445 initargs: dict[ str, __.typx.Any ] = { } 

446 if not __.is_absent( generalcfg ): 

447 initargs[ 'generalcfg' ] = generalcfg 

448 if not __.is_absent( modulecfgs ): 

449 initargs[ 'modulecfgs' ] = ModulesConfigurationsRegistry( 

450 { mname: configuration for mname, configuration 

451 in modulecfgs.items( ) } ) 

452 if not __.is_absent( printer_factory ): 

453 initargs[ 'printer_factory' ] = printer_factory 

454 _add_truck_initarg_active_flavors( 

455 initargs, active_flavors, evname_active_flavors ) 

456 _add_truck_initarg_trace_levels( 

457 initargs, trace_levels, evname_trace_levels ) 

458 return Truck( **initargs ) 

459 

460 

461@_validate_arguments 

462def register_module( 

463 name: RegisterModuleNameArgument = __.absent, 

464 flavors: ProduceTruckFlavorsArgument = __.absent, 

465 formatter_factory: RegisterModuleFormatterFactoryArgument = __.absent, 

466 include_context: RegisterModuleIncludeContextArgument = __.absent, 

467 prefix_emitter: RegisterModulePrefixEmitterArgument = __.absent, 

468) -> _cfg.ModuleConfiguration: 

469 ''' Registers module configuration on the builtin truck. 

470 

471 If no truck exists in builtins, installs one which produces null 

472 printers. 

473 

474 Intended for library developers to configure debugging flavors 

475 without overriding anything set by the application or other libraries. 

476 Application developers should call :py:func:`install` instead. 

477 ''' 

478 import builtins 

479 truck = getattr( builtins, builtins_alias_default, None ) 

480 if not isinstance( truck, Truck ): 

481 truck = Truck( ) 

482 __.install_builtin_safely( 

483 builtins_alias_default, 

484 truck, 

485 _exceptions.AttributeNondisplacement ) 

486 nomargs: dict[ str, __.typx.Any ] = { } 

487 if not __.is_absent( flavors ): 

488 nomargs[ 'flavors' ] = __.immut.Dictionary( flavors ) 

489 if not __.is_absent( formatter_factory ): 

490 nomargs[ 'formatter_factory' ] = formatter_factory 

491 if not __.is_absent( include_context ): 

492 nomargs[ 'include_context' ] = include_context 

493 if not __.is_absent( prefix_emitter ): 

494 nomargs[ 'prefix_emitter' ] = prefix_emitter 

495 configuration = _cfg.ModuleConfiguration( **nomargs ) 

496 return truck.register_module( name = name, configuration = configuration ) 

497 

498 

499def _add_truck_initarg_active_flavors( 

500 initargs: dict[ str, __.typx.Any ], 

501 active_flavors: ProduceTruckActiveFlavorsArgument = __.absent, 

502 evname_active_flavors: ProduceTruckEvnActiveFlavorsArgument = __.absent, 

503) -> None: 

504 name = 'active_flavors' 

505 if not __.is_absent( active_flavors ): 

506 if isinstance( active_flavors, Omniflavor ): 

507 initargs[ name ] = __.immut.Dictionary( 

508 { None: active_flavors } ) 

509 elif isinstance( active_flavors, ( __.cabc.Sequence, __.cabc.Set ) ): 

510 initargs[ name ] = __.immut.Dictionary( 

511 { None: frozenset( active_flavors ) } ) 

512 else: 

513 initargs[ name ] = __.immut.Dictionary( { 

514 mname: 

515 flavors if isinstance( flavors, Omniflavor ) 

516 else frozenset( flavors ) 

517 for mname, flavors in active_flavors.items( ) } ) 

518 elif evname_active_flavors is not None: 

519 initargs[ name ] = ( 

520 active_flavors_from_environment( evname = evname_active_flavors ) ) 

521 

522 

523def _add_truck_initarg_trace_levels( 

524 initargs: dict[ str, __.typx.Any ], 

525 trace_levels: ProduceTruckTraceLevelsArgument = __.absent, 

526 evname_trace_levels: ProduceTruckEvnTraceLevelsArgument = __.absent, 

527) -> None: 

528 name = 'trace_levels' 

529 if not __.is_absent( trace_levels ): 

530 if isinstance( trace_levels, int ): 

531 initargs[ name ] = __.immut.Dictionary( { None: trace_levels } ) 

532 else: 

533 trace_levels_: TraceLevelsRegistryLiberal = { None: -1 } 

534 trace_levels_.update( trace_levels ) 

535 initargs[ name ] = __.immut.Dictionary( trace_levels_ ) 

536 elif evname_trace_levels is not None: 

537 initargs[ name ] = ( 

538 trace_levels_from_environment( evname = evname_trace_levels ) ) 

539 

540 

541def _calculate_effective_flavors( 

542 flavors: ActiveFlavorsRegistry, mname: str 

543) -> ActiveFlavors: 

544 result_ = flavors.get( None ) or frozenset( ) 

545 if isinstance( result_, Omniflavor ): return result_ 

546 result = result_ 

547 for mname_ in _iterate_module_name_ancestry( mname ): 

548 if mname_ in flavors: 

549 result_ = flavors.get( mname_ ) or frozenset( ) 

550 if isinstance( result_, Omniflavor ): return result_ 

551 result |= result_ 

552 return result 

553 

554 

555def _calculate_effective_trace_level( 

556 levels: TraceLevelsRegistry, mname: str 

557) -> int: 

558 result = levels.get( None, -1 ) 

559 for mname_ in _iterate_module_name_ancestry( mname ): 

560 if mname_ in levels: 

561 result = levels[ mname_ ] 

562 return result 

563 

564 

565def _calculate_ic_initargs( 

566 truck: Truck, 

567 configuration: __.immut.Dictionary[ str, __.typx.Any ], 

568 control: _cfg.FormatterControl, 

569 mname: str, 

570 flavor: _cfg.Flavor, 

571) -> dict[ str, __.typx.Any ]: 

572 nomargs: dict[ str, __.typx.Any ] = { } 

573 nomargs[ 'argToStringFunction' ] = ( 

574 configuration[ 'formatter_factory' ]( control, mname, flavor ) ) 

575 nomargs[ 'includeContext' ] = configuration[ 'include_context' ] 

576 if isinstance( truck.printer_factory, __.io.TextIOBase ): 

577 printer = __.funct.partial( print, file = truck.printer_factory ) 

578 else: printer = truck.printer_factory( mname, flavor ) 

579 nomargs[ 'outputFunction' ] = printer 

580 prefix_emitter = configuration[ 'prefix_emitter' ] 

581 nomargs[ 'prefix' ] = ( 

582 prefix_emitter if isinstance( prefix_emitter, str ) 

583 else prefix_emitter( mname, flavor ) ) 

584 return nomargs 

585 

586 

587def _dict_from_dataclass( objct: object ) -> dict[ str, __.typx.Any ]: 

588 # objct = __.typx.cast( _typeshed.DataclassInstance, objct ) 

589 return { 

590 field.name: getattr( objct, field.name ) 

591 for field in __.dcls.fields( objct ) # pyright: ignore[reportArgumentType] 

592 if not field.name.startswith( '_' ) } 

593 

594 

595def _discover_invoker_module_name( ) -> str: 

596 frame = __.inspect.currentframe( ) 

597 while frame: # pragma: no branch 

598 module = __.inspect.getmodule( frame ) 

599 if module is None: 

600 if '<stdin>' == frame.f_code.co_filename: # pragma: no cover 

601 name = '__main__' 

602 break 

603 raise _exceptions.ModuleInferenceFailure 

604 name = module.__name__ 

605 if not name.startswith( f"{__.package_name}." ): break 

606 frame = frame.f_back 

607 return name 

608 

609 

610def _iterate_module_name_ancestry( name: str ) -> __.cabc.Iterator[ str ]: 

611 parts = name.split( '.' ) 

612 for i in range( len( parts ) ): 

613 yield '.'.join( parts[ : i + 1 ] ) 

614 

615 

616def _merge_ic_configuration( 

617 base: dict[ str, __.typx.Any ], update_objct: object, 

618) -> dict[ str, __.typx.Any ]: 

619 update: dict[ str, __.typx.Any ] = _dict_from_dataclass( update_objct ) 

620 result: dict[ str, __.typx.Any ] = { } 

621 result[ 'flavors' ] = ( 

622 dict( base.get( 'flavors', dict( ) ) ) 

623 | dict( update.get( 'flavors', dict( ) ) ) ) 

624 for ename in ( 'formatter_factory', 'include_context', 'prefix_emitter' ): 

625 uvalue = update.get( ename ) 

626 if uvalue is not None: result[ ename ] = uvalue 

627 elif ename in base: result[ ename ] = base[ ename ] 

628 return result 

629 

630 

631def _produce_ic_configuration( 

632 vehicle: Truck, mname: str, flavor: _cfg.Flavor 

633) -> __.immut.Dictionary[ str, __.typx.Any ]: 

634 fconfigs: list[ _cfg.FlavorConfiguration ] = [ ] 

635 vconfig = vehicle.generalcfg 

636 configd: dict[ str, __.typx.Any ] = { 

637 field.name: getattr( vconfig, field.name ) 

638 for field in __.dcls.fields( vconfig ) 

639 if not field.name.startswith( '_' ) } 

640 if flavor in vconfig.flavors: 

641 fconfigs.append( vconfig.flavors[ flavor ] ) 

642 for mname_ in _iterate_module_name_ancestry( mname ): 

643 if mname_ not in vehicle.modulecfgs: continue 

644 mconfig = vehicle.modulecfgs[ mname_ ] 

645 configd = _merge_ic_configuration( configd, mconfig ) 

646 if flavor in mconfig.flavors: 

647 fconfigs.append( mconfig.flavors[ flavor ] ) 

648 if not fconfigs: raise _exceptions.FlavorInavailability( flavor ) 

649 # Apply collected flavor configs after general and module configs. 

650 # (Applied in top-down order for correct overrides.) 

651 for fconfig in fconfigs: 

652 configd = _merge_ic_configuration( configd, fconfig ) 

653 return __.immut.Dictionary( configd )