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

234 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-04-07 00:37 +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 

24from __future__ import annotations 

25 

26import icecream as _icecream 

27 

28from . import __ 

29from . import configuration as _cfg 

30from . import exceptions as _exceptions 

31from . import printers as _printers 

32 

33 

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

35 import _typeshed 

36 

37 

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

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

40_self_modulecfg: _cfg.ModuleConfiguration = _cfg.ModuleConfiguration( 

41 flavors = __.ImmutableDictionary( 

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

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

44_validate_arguments = ( 

45 __.validate_arguments( 

46 globalvars = globals( ), 

47 errorclass = _exceptions.ArgumentClassInvalidity ) ) 

48 

49 

50class ModulesConfigurationsRegistry( 

51 __.AccretiveDictionary[ str, _cfg.ModuleConfiguration ] 

52): 

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

54 

55 def __init__( 

56 self, 

57 *iterables: __.DictionaryPositionalArgument[ 

58 str, _cfg.ModuleConfiguration ], 

59 **entries: __.DictionaryNominativeArgument[ 

60 _cfg.ModuleConfiguration ], 

61 ): 

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

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

64 

65 

66class Omniflavor( __.enum.Enum ): 

67 ''' Singleton to match any flavor. ''' 

68 

69 Instance = __.enum.auto( ) 

70 

71 

72builtins_alias_default: __.typx.Annotated[ 

73 str, 

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

75] = 'ictr' 

76modulecfgs: __.typx.Annotated[ 

77 ModulesConfigurationsRegistry, 

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

79] = ModulesConfigurationsRegistry( ) 

80omniflavor: __.typx.Annotated[ 

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

82] = Omniflavor.Instance 

83 

84 

85class Truck( metaclass = __.ImmutableCompleteDataclass ): 

86 ''' Vends flavors of Icecream debugger. ''' 

87 

88 active_flavors: __.typx.Annotated[ 

89 ActiveFlavorsRegistry, 

90 __.typx.Doc( 

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

92 

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

94 override globals for that module. 

95 ''' ), 

96 ] = __.dcls.field( default_factory = __.ImmutableDictionary ) # pyright: ignore 

97 generalcfg: __.typx.Annotated[ 

98 _cfg.VehicleConfiguration, 

99 __.typx.Doc( 

100 ''' General configuration. 

101 

102 Top of configuration inheritance hierarchy. 

103 Default is suitable for application use. 

104 ''' ), 

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

106 modulecfgs: __.typx.Annotated[ 

107 ModulesConfigurationsRegistry, 

108 __.typx.Doc( 

109 ''' Registry of per-module configurations. 

110 

111 Modules inherit configuration from their parent packages. 

112 Top-level packages inherit from general instance 

113 configruration. 

114 ''' ), 

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

116 printer_factory: __.typx.Annotated[ 

117 _printers.PrinterFactoryUnion, 

118 __.typx.Doc( 

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

120 

121 May also be writable text stream. 

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

123 return a callable which takes one argument, the string 

124 produced by a formatter. 

125 ''' ), 

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

127 trace_levels: __.typx.Annotated[ 

128 TraceLevelsRegistry, 

129 __.typx.Doc( 

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

131 

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

133 override globals for that module. 

134 ''' ), 

135 ] = __.dcls.field( 

136 default_factory = lambda: __.ImmutableDictionary( { None: -1 } ) ) 

137 _debuggers: __.typx.Annotated[ 

138 __.AccretiveDictionary[ 

139 tuple[ str, _cfg.Flavor ], _icecream.IceCreamDebugger ], 

140 __.typx.Doc( 

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

142 ] = __.dcls.field( default_factory = __.AccretiveDictionary ) # pyright: ignore 

143 _debuggers_lock: __.typx.Annotated[ 

144 __.threads.Lock, 

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

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

147 

148 @_validate_arguments 

149 def __call__( 

150 self, 

151 flavor: _cfg.Flavor, *, 

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

153 ) -> _icecream.IceCreamDebugger: 

154 ''' Vends flavor of Icecream debugger. ''' 

155 mname = ( 

156 _discover_invoker_module_name( ) if __.is_absent( module_name ) 

157 else module_name ) 

158 cache_index = ( mname, flavor ) 

159 if cache_index in self._debuggers: 

160 with self._debuggers_lock: 

161 return self._debuggers[ cache_index ] 

162 configuration = _produce_ic_configuration( self, mname, flavor ) 

163 control = _cfg.FormatterControl( ) 

164 initargs = _calculate_ic_initargs( 

165 self, configuration, control, mname, flavor ) 

166 debugger = _icecream.IceCreamDebugger( **initargs ) 

167 if isinstance( flavor, int ): 

168 trace_level = ( 

169 _calculate_effective_trace_level( self.trace_levels, mname) ) 

170 debugger.enabled = flavor <= trace_level 

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

172 active_flavors = ( 

173 _calculate_effective_flavors( self.active_flavors, mname ) ) 

174 debugger.enabled = ( 

175 isinstance( active_flavors, Omniflavor ) 

176 or flavor in active_flavors ) 

177 with self._debuggers_lock: 

178 self._debuggers[ cache_index ] = debugger 

179 return debugger 

180 

181 @_validate_arguments 

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

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

184 

185 Replaces an existing truck. Preserves global module configurations. 

186 

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

188 ''' 

189 import builtins 

190 with _installer_lock: 

191 truck_o = getattr( builtins, alias, None ) 

192 if isinstance( truck_o, Truck ): 

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

194 'Installed truck is being replaced.' ) 

195 setattr( builtins, alias, self ) 

196 else: 

197 __.install_builtin_safely( 

198 alias, self, _exceptions.AttributeNondisplacement ) 

199 return self 

200 

201 @_validate_arguments 

202 def register_module( 

203 self, 

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

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

206 ) -> __.typx.Self: 

207 ''' Registers configuration for module. 

208 

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

210 inferred. 

211 

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

213 ''' 

214 if __.is_absent( name ): 

215 name = _discover_invoker_module_name( ) 

216 if __.is_absent( configuration ): 

217 configuration = _cfg.ModuleConfiguration( ) 

218 with _registrar_lock: 

219 self.modulecfgs[ name ] = configuration 

220 return self 

221 

222 

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

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

225 Omniflavor, 

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

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

228] 

229ActiveFlavorsRegistry: __.typx.TypeAlias = ( 

230 __.ImmutableDictionary[ str | None, ActiveFlavors ] ) 

231ActiveFlavorsRegistryLiberal: __.typx.TypeAlias = ( 

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

233ModulesConfigurationsRegistryLiberal: __.typx.TypeAlias = ( 

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

235TraceLevelsRegistry: __.typx.TypeAlias = ( 

236 __.ImmutableDictionary[ str | None, int ] ) 

237TraceLevelsRegistryLiberal: __.typx.TypeAlias = ( 

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

239 

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

241 str, 

242 __.typx.Doc( 

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

244] 

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

246 __.Absential[ ActiveFlavorsLiberal | ActiveFlavorsRegistryLiberal ], 

247 __.typx.Doc( 

248 ''' Flavors to activate. 

249 

250 Can be collection, which applies globally across all registered 

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

252 

253 Module-specific entries merge with global entries. 

254 ''' ), 

255] 

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

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

258 __.typx.Doc( 

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

260 

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

262 

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

264 environment. 

265 

266 If active flavors are supplied directly to a function, 

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

268 parsed from the process environment. 

269 ''' ), 

270] 

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

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

273 __.typx.Doc( 

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

275 

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

277 

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

279 environment. 

280 

281 If trace levels are supplied directly to a function, 

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

283 parsed from the process environment. 

284 ''' ), 

285] 

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

287 __.Absential[ _cfg.FlavorsRegistryLiberal ], 

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

289] 

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

291 __.Absential[ _cfg.VehicleConfiguration ], 

292 __.typx.Doc( 

293 ''' General configuration for the truck. 

294 

295 Top of configuration inheritance hierarchy. If absent, 

296 defaults to a suitable configuration for application use. 

297 ''' ), 

298] 

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

300 __.Absential[ ModulesConfigurationsRegistryLiberal ], 

301 __.typx.Doc( 

302 ''' Module configurations for the truck. 

303 

304 If absent, defaults to global modules registry. 

305 ''' ), 

306] 

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

308 __.Absential[ _printers.PrinterFactoryUnion ], 

309 __.typx.Doc( 

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

311 

312 May also be writable text stream. 

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

314 return a callable which takes one argument, the string 

315 produced by a formatter. 

316 

317 If absent, uses a default. 

318 ''' ), 

319] 

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

321 __.Absential[ int | TraceLevelsRegistryLiberal ], 

322 __.typx.Doc( 

323 ''' Maximum trace depths. 

324 

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

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

327 

328 Module-specific entries override global entries. 

329 ''' ), 

330] 

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

332 __.Absential[ _cfg.FormatterFactory ], 

333 __.typx.Doc( 

334 ''' Factory which produces formatter callable. 

335 

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

337 Returns formatter to convert an argument to a string. 

338 ''' ), 

339] 

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

341 __.Absential[ bool ], 

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

343] 

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

345 __.Absential[ str ], 

346 __.typx.Doc( 

347 ''' Name of the module to register. 

348 

349 If absent, infers the current module name. 

350 ''' ), 

351] 

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

353 __.Absential[ _cfg.PrefixEmitterUnion ], 

354 __.typx.Doc( 

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

356 

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

358 arguments. Returns prefix string. 

359 ''' ), 

360] 

361 

362 

363def active_flavors_from_environment( 

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

365) -> ActiveFlavorsRegistry: 

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

367 active_flavors: ActiveFlavorsRegistryLiberal = { } 

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

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

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

371 if not part: continue 

372 if ':' in part: 

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

374 else: mname, flavors = None, part 

375 match flavors: 

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

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

378 return __.ImmutableDictionary( { 

379 mname: 

380 flavors if isinstance( flavors, Omniflavor ) 

381 else frozenset( flavors ) 

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

383 

384 

385def trace_levels_from_environment( 

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

387) -> TraceLevelsRegistry: 

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

389 trace_levels: TraceLevelsRegistryLiberal = { None: -1 } 

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

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

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

393 if not part: continue 

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

395 else: mname, level = None, part 

396 if not level.isdigit( ): 

397 __.warnings.warn( 

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

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

400 continue 

401 trace_levels[ mname ] = int( level ) 

402 return __.ImmutableDictionary( trace_levels ) 

403 

404 

405@_validate_arguments 

406def install( # noqa: PLR0913 

407 alias: InstallAliasArgument = builtins_alias_default, 

408 active_flavors: ProduceTruckActiveFlavorsArgument = __.absent, 

409 generalcfg: ProduceTruckGeneralcfgArgument = __.absent, 

410 printer_factory: ProduceTruckPrinterFactoryArgument = __.absent, 

411 trace_levels: ProduceTruckTraceLevelsArgument = __.absent, 

412 evname_active_flavors: ProduceTruckEvnActiveFlavorsArgument = __.absent, 

413 evname_trace_levels: ProduceTruckEvnTraceLevelsArgument = __.absent, 

414) -> Truck: 

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

416 

417 Replaces an existing truck, preserving global module configurations. 

418 

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

420 ''' 

421 truck = produce_truck( 

422 active_flavors = active_flavors, 

423 generalcfg = generalcfg, 

424 printer_factory = printer_factory, 

425 trace_levels = trace_levels, 

426 evname_active_flavors = evname_active_flavors, 

427 evname_trace_levels = evname_trace_levels ) 

428 return truck.install( alias = alias ) 

429 

430 

431@_validate_arguments 

432def produce_truck( # noqa: PLR0913 

433 active_flavors: ProduceTruckActiveFlavorsArgument = __.absent, 

434 generalcfg: ProduceTruckGeneralcfgArgument = __.absent, 

435 modulecfgs: ProduceTruckModulecfgsArgument = __.absent, 

436 printer_factory: ProduceTruckPrinterFactoryArgument = __.absent, 

437 trace_levels: ProduceTruckTraceLevelsArgument = __.absent, 

438 evname_active_flavors: ProduceTruckEvnActiveFlavorsArgument = __.absent, 

439 evname_trace_levels: ProduceTruckEvnTraceLevelsArgument = __.absent, 

440) -> Truck: 

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

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

443 # TODO: Deeper validation of printer factory. 

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

445 if not __.is_absent( generalcfg ): 

446 initargs[ 'generalcfg' ] = generalcfg 

447 if not __.is_absent( modulecfgs ): 

448 initargs[ 'modulecfgs' ] = ModulesConfigurationsRegistry( 

449 { mname: configuration for mname, configuration 

450 in modulecfgs.items( ) } ) 

451 if not __.is_absent( printer_factory ): 

452 initargs[ 'printer_factory' ] = printer_factory 

453 _add_truck_initarg_active_flavors( 

454 initargs, active_flavors, evname_active_flavors ) 

455 _add_truck_initarg_trace_levels( 

456 initargs, trace_levels, evname_trace_levels ) 

457 return Truck( **initargs ) 

458 

459 

460@_validate_arguments 

461def register_module( 

462 name: RegisterModuleNameArgument = __.absent, 

463 flavors: ProduceTruckFlavorsArgument = __.absent, 

464 formatter_factory: RegisterModuleFormatterFactoryArgument = __.absent, 

465 include_context: RegisterModuleIncludeContextArgument = __.absent, 

466 prefix_emitter: RegisterModulePrefixEmitterArgument = __.absent, 

467) -> _cfg.ModuleConfiguration: 

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

469 

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

471 printers. 

472 

473 Intended for library developers to configure debugging flavors 

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

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

476 ''' 

477 import builtins 

478 truck = getattr( builtins, builtins_alias_default, None ) 

479 if not isinstance( truck, Truck ): 

480 truck = Truck( ) 

481 __.install_builtin_safely( 

482 builtins_alias_default, 

483 truck, 

484 _exceptions.AttributeNondisplacement ) 

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

486 if not __.is_absent( flavors ): 

487 nomargs[ 'flavors' ] = __.ImmutableDictionary( flavors ) 

488 if not __.is_absent( formatter_factory ): 

489 nomargs[ 'formatter_factory' ] = formatter_factory 

490 if not __.is_absent( include_context ): 

491 nomargs[ 'include_context' ] = include_context 

492 if not __.is_absent( prefix_emitter ): 

493 nomargs[ 'prefix_emitter' ] = prefix_emitter 

494 configuration = _cfg.ModuleConfiguration( **nomargs ) 

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

496 

497 

498def _add_truck_initarg_active_flavors( 

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

500 active_flavors: ProduceTruckActiveFlavorsArgument = __.absent, 

501 evname_active_flavors: ProduceTruckEvnActiveFlavorsArgument = __.absent, 

502) -> None: 

503 name = 'active_flavors' 

504 if not __.is_absent( active_flavors ): 

505 if isinstance( active_flavors, Omniflavor ): 

506 initargs[ name ] = __.ImmutableDictionary( 

507 { None: active_flavors } ) 

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

509 initargs[ name ] = __.ImmutableDictionary( 

510 { None: frozenset( active_flavors ) } ) 

511 else: 

512 initargs[ name ] = __.ImmutableDictionary( { 

513 mname: 

514 flavors if isinstance( flavors, Omniflavor ) 

515 else frozenset( flavors ) 

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

517 elif evname_active_flavors is not None: 

518 initargs[ name ] = ( 

519 active_flavors_from_environment( evname = evname_active_flavors ) ) 

520 

521 

522def _add_truck_initarg_trace_levels( 

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

524 trace_levels: ProduceTruckTraceLevelsArgument = __.absent, 

525 evname_trace_levels: ProduceTruckEvnTraceLevelsArgument = __.absent, 

526) -> None: 

527 name = 'trace_levels' 

528 if not __.is_absent( trace_levels ): 

529 if isinstance( trace_levels, int ): 

530 initargs[ name ] = __.ImmutableDictionary( { None: trace_levels } ) 

531 else: 

532 trace_levels_: TraceLevelsRegistryLiberal = { None: -1 } 

533 trace_levels_.update( trace_levels ) 

534 initargs[ name ] = __.ImmutableDictionary( trace_levels_ ) 

535 elif evname_trace_levels is not None: 

536 initargs[ name ] = ( 

537 trace_levels_from_environment( evname = evname_trace_levels ) ) 

538 

539 

540def _calculate_effective_flavors( 

541 flavors: ActiveFlavorsRegistry, mname: str 

542) -> ActiveFlavors: 

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

544 if isinstance( result_, Omniflavor ): return result_ 

545 result = result_ 

546 for mname_ in _iterate_module_name_ancestry( mname ): 

547 if mname_ in flavors: 

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

549 if isinstance( result_, Omniflavor ): return result_ 

550 result |= result_ 

551 return result 

552 

553 

554def _calculate_effective_trace_level( 

555 levels: TraceLevelsRegistry, mname: str 

556) -> int: 

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

558 for mname_ in _iterate_module_name_ancestry( mname ): 

559 if mname_ in levels: 

560 result = levels[ mname_ ] 

561 return result 

562 

563 

564def _calculate_ic_initargs( 

565 truck: Truck, 

566 configuration: __.ImmutableDictionary[ str, __.typx.Any ], 

567 control: _cfg.FormatterControl, 

568 mname: str, 

569 flavor: _cfg.Flavor, 

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

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

572 nomargs[ 'argToStringFunction' ] = ( 

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

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

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

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

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

578 nomargs[ 'outputFunction' ] = printer 

579 prefix_emitter = configuration[ 'prefix_emitter' ] 

580 nomargs[ 'prefix' ] = ( 

581 prefix_emitter if isinstance( prefix_emitter, str ) 

582 else prefix_emitter( mname, flavor ) ) 

583 return nomargs 

584 

585 

586def _dict_from_dataclass( 

587 obj: _typeshed.DataclassInstance 

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

589 return { 

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

591 for field in __.dcls.fields( obj ) } 

592 

593 

594def _discover_invoker_module_name( ) -> str: 

595 frame = __.inspect.currentframe( ) 

596 while frame: # pragma: no branch 

597 module = __.inspect.getmodule( frame ) 

598 if module is None: 

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

600 name = '__main__' 

601 break 

602 raise _exceptions.ModuleInferenceFailure 

603 name = module.__name__ 

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

605 frame = frame.f_back 

606 return name 

607 

608 

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

610 parts = name.split( '.' ) 

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

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

613 

614 

615def _merge_ic_configuration( 

616 base: dict[ str, __.typx.Any ], update_obj: _typeshed.DataclassInstance 

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

618 update: dict[ str, __.typx.Any ] = _dict_from_dataclass( update_obj ) 

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

620 result[ 'flavors' ] = ( 

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

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

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

624 uvalue = update.get( ename ) 

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

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

627 return result 

628 

629 

630def _produce_ic_configuration( 

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

632) -> __.ImmutableDictionary[ str, __.typx.Any ]: 

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

634 vconfig = vehicle.generalcfg 

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

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

637 for field in __.dcls.fields( vconfig ) } 

638 if flavor in vconfig.flavors: 

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

640 for mname_ in _iterate_module_name_ancestry( mname ): 

641 if mname_ not in vehicle.modulecfgs: continue 

642 mconfig = vehicle.modulecfgs[ mname_ ] 

643 configd = _merge_ic_configuration( configd, mconfig ) 

644 if flavor in mconfig.flavors: 

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

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

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

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

649 for fconfig in fconfigs: 

650 configd = _merge_ic_configuration( configd, fconfig ) 

651 return __.ImmutableDictionary( configd )