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

235 statements  

« prev     ^ index     » next       coverage.py v7.8.2, created at 2025-06-06 03:29 +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 

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

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

74 Omniflavor, 

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

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

77] 

78ActiveFlavorsRegistry: __.typx.TypeAlias = ( 

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

80ActiveFlavorsRegistryLiberal: __.typx.TypeAlias = ( 

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

82ModulesConfigurationsRegistryLiberal: __.typx.TypeAlias = ( 

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

84ReportersRegistry: __.typx.TypeAlias = ( 

85 __.AccretiveDictionary[ 

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

87TraceLevelsRegistry: __.typx.TypeAlias = ( 

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

89TraceLevelsRegistryLiberal: __.typx.TypeAlias = ( 

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

91 

92 

93builtins_alias_default: __.typx.Annotated[ 

94 str, 

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

96] = 'ictr' 

97modulecfgs: __.typx.Annotated[ 

98 ModulesConfigurationsRegistry, 

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

100] = ModulesConfigurationsRegistry( ) 

101omniflavor: __.typx.Annotated[ 

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

103] = Omniflavor.Instance 

104 

105 

106class Truck( metaclass = __.ImmutableCompleteDataclass ): 

107 ''' Vends flavors of Icecream debugger. ''' 

108 

109 active_flavors: __.typx.Annotated[ 

110 ActiveFlavorsRegistry, 

111 __.typx.Doc( 

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

113 

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

115 override globals for that module. 

116 ''' ), 

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

118 generalcfg: __.typx.Annotated[ 

119 _cfg.VehicleConfiguration, 

120 __.typx.Doc( 

121 ''' General configuration. 

122 

123 Top of configuration inheritance hierarchy. 

124 Default is suitable for application use. 

125 ''' ), 

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

127 modulecfgs: __.typx.Annotated[ 

128 ModulesConfigurationsRegistry, 

129 __.typx.Doc( 

130 ''' Registry of per-module configurations. 

131 

132 Modules inherit configuration from their parent packages. 

133 Top-level packages inherit from general instance 

134 configruration. 

135 ''' ), 

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

137 printer_factory: __.typx.Annotated[ 

138 _printers.PrinterFactoryUnion, 

139 __.typx.Doc( 

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

141 

142 May also be writable text stream. 

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

144 return a callable which takes one argument, the string 

145 produced by a formatter. 

146 ''' ), 

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

148 trace_levels: __.typx.Annotated[ 

149 TraceLevelsRegistry, 

150 __.typx.Doc( 

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

152 

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

154 override globals for that module. 

155 ''' ), 

156 ] = __.dcls.field( 

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

158 _debuggers: __.typx.Annotated[ 

159 ReportersRegistry, 

160 __.typx.Doc( 

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

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

163 _debuggers_lock: __.typx.Annotated[ 

164 __.threads.Lock, 

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

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

167 

168 @_validate_arguments 

169 def __call__( 

170 self, 

171 flavor: _cfg.Flavor, *, 

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

173 ) -> _icecream.IceCreamDebugger: 

174 ''' Vends flavor of Icecream debugger. ''' 

175 mname = ( 

176 _discover_invoker_module_name( ) if __.is_absent( module_name ) 

177 else module_name ) 

178 cache_index = ( mname, flavor ) 

179 if cache_index in self._debuggers: 

180 with self._debuggers_lock: 

181 return self._debuggers[ cache_index ] 

182 configuration = _produce_ic_configuration( self, mname, flavor ) 

183 control = _cfg.FormatterControl( ) 

184 initargs = _calculate_ic_initargs( 

185 self, configuration, control, mname, flavor ) 

186 debugger = _icecream.IceCreamDebugger( **initargs ) 

187 if isinstance( flavor, int ): 

188 trace_level = ( 

189 _calculate_effective_trace_level( self.trace_levels, mname) ) 

190 debugger.enabled = flavor <= trace_level 

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

192 active_flavors = ( 

193 _calculate_effective_flavors( self.active_flavors, mname ) ) 

194 debugger.enabled = ( 

195 isinstance( active_flavors, Omniflavor ) 

196 or flavor in active_flavors ) 

197 with self._debuggers_lock: 

198 self._debuggers[ cache_index ] = debugger 

199 return debugger 

200 

201 @_validate_arguments 

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

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

204 

205 Replaces an existing truck. Preserves global module configurations. 

206 

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

208 ''' 

209 import builtins 

210 with _installer_lock: 

211 truck_o = getattr( builtins, alias, None ) 

212 if isinstance( truck_o, Truck ): 

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

214 'Installed truck is being replaced.' ) 

215 setattr( builtins, alias, self ) 

216 else: 

217 __.install_builtin_safely( 

218 alias, self, _exceptions.AttributeNondisplacement ) 

219 return self 

220 

221 @_validate_arguments 

222 def register_module( 

223 self, 

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

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

226 ) -> __.typx.Self: 

227 ''' Registers configuration for module. 

228 

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

230 inferred. 

231 

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

233 ''' 

234 if __.is_absent( name ): 

235 name = _discover_invoker_module_name( ) 

236 if __.is_absent( configuration ): 

237 configuration = _cfg.ModuleConfiguration( ) 

238 with _registrar_lock: 

239 self.modulecfgs[ name ] = configuration 

240 return self 

241 

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

243 str, 

244 __.typx.Doc( 

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

246] 

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

248 __.Absential[ ActiveFlavorsLiberal | ActiveFlavorsRegistryLiberal ], 

249 __.typx.Doc( 

250 ''' Flavors to activate. 

251 

252 Can be collection, which applies globally across all registered 

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

254 

255 Module-specific entries merge with global entries. 

256 ''' ), 

257] 

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

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

260 __.typx.Doc( 

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

262 

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

264 

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

266 environment. 

267 

268 If active flavors are supplied directly to a function, 

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

270 parsed from the process environment. 

271 ''' ), 

272] 

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

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

275 __.typx.Doc( 

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

277 

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

279 

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

281 environment. 

282 

283 If trace levels are supplied directly to a function, 

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

285 parsed from the process environment. 

286 ''' ), 

287] 

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

289 __.Absential[ _cfg.FlavorsRegistryLiberal ], 

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

291] 

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

293 __.Absential[ _cfg.VehicleConfiguration ], 

294 __.typx.Doc( 

295 ''' General configuration for the truck. 

296 

297 Top of configuration inheritance hierarchy. If absent, 

298 defaults to a suitable configuration for application use. 

299 ''' ), 

300] 

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

302 __.Absential[ ModulesConfigurationsRegistryLiberal ], 

303 __.typx.Doc( 

304 ''' Module configurations for the truck. 

305 

306 If absent, defaults to global modules registry. 

307 ''' ), 

308] 

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

310 __.Absential[ _printers.PrinterFactoryUnion ], 

311 __.typx.Doc( 

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

313 

314 May also be writable text stream. 

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

316 return a callable which takes one argument, the string 

317 produced by a formatter. 

318 

319 If absent, uses a default. 

320 ''' ), 

321] 

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

323 __.Absential[ int | TraceLevelsRegistryLiberal ], 

324 __.typx.Doc( 

325 ''' Maximum trace depths. 

326 

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

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

329 

330 Module-specific entries override global entries. 

331 ''' ), 

332] 

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

334 __.Absential[ _cfg.FormatterFactory ], 

335 __.typx.Doc( 

336 ''' Factory which produces formatter callable. 

337 

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

339 Returns formatter to convert an argument to a string. 

340 ''' ), 

341] 

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

343 __.Absential[ bool ], 

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

345] 

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

347 __.Absential[ str ], 

348 __.typx.Doc( 

349 ''' Name of the module to register. 

350 

351 If absent, infers the current module name. 

352 ''' ), 

353] 

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

355 __.Absential[ _cfg.PrefixEmitterUnion ], 

356 __.typx.Doc( 

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

358 

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

360 arguments. Returns prefix string. 

361 ''' ), 

362] 

363 

364 

365def active_flavors_from_environment( 

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

367) -> ActiveFlavorsRegistry: 

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

369 active_flavors: ActiveFlavorsRegistryLiberal = { } 

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

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

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

373 if not part: continue 

374 if ':' in part: 

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

376 else: mname, flavors = None, part 

377 match flavors: 

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

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

380 return __.ImmutableDictionary( { 

381 mname: 

382 flavors if isinstance( flavors, Omniflavor ) 

383 else frozenset( flavors ) 

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

385 

386 

387def trace_levels_from_environment( 

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

389) -> TraceLevelsRegistry: 

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

391 trace_levels: TraceLevelsRegistryLiberal = { None: -1 } 

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

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

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

395 if not part: continue 

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

397 else: mname, level = None, part 

398 if not level.isdigit( ): 

399 __.warnings.warn( 

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

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

402 continue 

403 trace_levels[ mname ] = int( level ) 

404 return __.ImmutableDictionary( trace_levels ) 

405 

406 

407@_validate_arguments 

408def install( # noqa: PLR0913 

409 alias: InstallAliasArgument = builtins_alias_default, 

410 active_flavors: ProduceTruckActiveFlavorsArgument = __.absent, 

411 generalcfg: ProduceTruckGeneralcfgArgument = __.absent, 

412 printer_factory: ProduceTruckPrinterFactoryArgument = __.absent, 

413 trace_levels: ProduceTruckTraceLevelsArgument = __.absent, 

414 evname_active_flavors: ProduceTruckEvnActiveFlavorsArgument = __.absent, 

415 evname_trace_levels: ProduceTruckEvnTraceLevelsArgument = __.absent, 

416) -> Truck: 

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

418 

419 Replaces an existing truck, preserving global module configurations. 

420 

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

422 ''' 

423 truck = produce_truck( 

424 active_flavors = active_flavors, 

425 generalcfg = generalcfg, 

426 printer_factory = printer_factory, 

427 trace_levels = trace_levels, 

428 evname_active_flavors = evname_active_flavors, 

429 evname_trace_levels = evname_trace_levels ) 

430 return truck.install( alias = alias ) 

431 

432 

433@_validate_arguments 

434def produce_truck( # noqa: PLR0913 

435 active_flavors: ProduceTruckActiveFlavorsArgument = __.absent, 

436 generalcfg: ProduceTruckGeneralcfgArgument = __.absent, 

437 modulecfgs: ProduceTruckModulecfgsArgument = __.absent, 

438 printer_factory: ProduceTruckPrinterFactoryArgument = __.absent, 

439 trace_levels: ProduceTruckTraceLevelsArgument = __.absent, 

440 evname_active_flavors: ProduceTruckEvnActiveFlavorsArgument = __.absent, 

441 evname_trace_levels: ProduceTruckEvnTraceLevelsArgument = __.absent, 

442) -> Truck: 

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

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

445 # TODO: Deeper validation of printer factory. 

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

447 if not __.is_absent( generalcfg ): 

448 initargs[ 'generalcfg' ] = generalcfg 

449 if not __.is_absent( modulecfgs ): 

450 initargs[ 'modulecfgs' ] = ModulesConfigurationsRegistry( 

451 { mname: configuration for mname, configuration 

452 in modulecfgs.items( ) } ) 

453 if not __.is_absent( printer_factory ): 

454 initargs[ 'printer_factory' ] = printer_factory 

455 _add_truck_initarg_active_flavors( 

456 initargs, active_flavors, evname_active_flavors ) 

457 _add_truck_initarg_trace_levels( 

458 initargs, trace_levels, evname_trace_levels ) 

459 return Truck( **initargs ) 

460 

461 

462@_validate_arguments 

463def register_module( 

464 name: RegisterModuleNameArgument = __.absent, 

465 flavors: ProduceTruckFlavorsArgument = __.absent, 

466 formatter_factory: RegisterModuleFormatterFactoryArgument = __.absent, 

467 include_context: RegisterModuleIncludeContextArgument = __.absent, 

468 prefix_emitter: RegisterModulePrefixEmitterArgument = __.absent, 

469) -> _cfg.ModuleConfiguration: 

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

471 

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

473 printers. 

474 

475 Intended for library developers to configure debugging flavors 

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

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

478 ''' 

479 import builtins 

480 truck = getattr( builtins, builtins_alias_default, None ) 

481 if not isinstance( truck, Truck ): 

482 truck = Truck( ) 

483 __.install_builtin_safely( 

484 builtins_alias_default, 

485 truck, 

486 _exceptions.AttributeNondisplacement ) 

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

488 if not __.is_absent( flavors ): 

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

490 if not __.is_absent( formatter_factory ): 

491 nomargs[ 'formatter_factory' ] = formatter_factory 

492 if not __.is_absent( include_context ): 

493 nomargs[ 'include_context' ] = include_context 

494 if not __.is_absent( prefix_emitter ): 

495 nomargs[ 'prefix_emitter' ] = prefix_emitter 

496 configuration = _cfg.ModuleConfiguration( **nomargs ) 

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

498 

499 

500def _add_truck_initarg_active_flavors( 

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

502 active_flavors: ProduceTruckActiveFlavorsArgument = __.absent, 

503 evname_active_flavors: ProduceTruckEvnActiveFlavorsArgument = __.absent, 

504) -> None: 

505 name = 'active_flavors' 

506 if not __.is_absent( active_flavors ): 

507 if isinstance( active_flavors, Omniflavor ): 

508 initargs[ name ] = __.ImmutableDictionary( 

509 { None: active_flavors } ) 

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

511 initargs[ name ] = __.ImmutableDictionary( 

512 { None: frozenset( active_flavors ) } ) 

513 else: 

514 initargs[ name ] = __.ImmutableDictionary( { 

515 mname: 

516 flavors if isinstance( flavors, Omniflavor ) 

517 else frozenset( flavors ) 

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

519 elif evname_active_flavors is not None: 

520 initargs[ name ] = ( 

521 active_flavors_from_environment( evname = evname_active_flavors ) ) 

522 

523 

524def _add_truck_initarg_trace_levels( 

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

526 trace_levels: ProduceTruckTraceLevelsArgument = __.absent, 

527 evname_trace_levels: ProduceTruckEvnTraceLevelsArgument = __.absent, 

528) -> None: 

529 name = 'trace_levels' 

530 if not __.is_absent( trace_levels ): 

531 if isinstance( trace_levels, int ): 

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

533 else: 

534 trace_levels_: TraceLevelsRegistryLiberal = { None: -1 } 

535 trace_levels_.update( trace_levels ) 

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

537 elif evname_trace_levels is not None: 

538 initargs[ name ] = ( 

539 trace_levels_from_environment( evname = evname_trace_levels ) ) 

540 

541 

542def _calculate_effective_flavors( 

543 flavors: ActiveFlavorsRegistry, mname: str 

544) -> ActiveFlavors: 

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

546 if isinstance( result_, Omniflavor ): return result_ 

547 result = result_ 

548 for mname_ in _iterate_module_name_ancestry( mname ): 

549 if mname_ in flavors: 

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

551 if isinstance( result_, Omniflavor ): return result_ 

552 result |= result_ 

553 return result 

554 

555 

556def _calculate_effective_trace_level( 

557 levels: TraceLevelsRegistry, mname: str 

558) -> int: 

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

560 for mname_ in _iterate_module_name_ancestry( mname ): 

561 if mname_ in levels: 

562 result = levels[ mname_ ] 

563 return result 

564 

565 

566def _calculate_ic_initargs( 

567 truck: Truck, 

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

569 control: _cfg.FormatterControl, 

570 mname: str, 

571 flavor: _cfg.Flavor, 

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

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

574 nomargs[ 'argToStringFunction' ] = ( 

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

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

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

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

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

580 nomargs[ 'outputFunction' ] = printer 

581 prefix_emitter = configuration[ 'prefix_emitter' ] 

582 nomargs[ 'prefix' ] = ( 

583 prefix_emitter if isinstance( prefix_emitter, str ) 

584 else prefix_emitter( mname, flavor ) ) 

585 return nomargs 

586 

587 

588def _dict_from_dataclass( 

589 obj: _typeshed.DataclassInstance 

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

591 return { 

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

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

594 

595 

596def _discover_invoker_module_name( ) -> str: 

597 frame = __.inspect.currentframe( ) 

598 while frame: # pragma: no branch 

599 module = __.inspect.getmodule( frame ) 

600 if module is None: 

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

602 name = '__main__' 

603 break 

604 raise _exceptions.ModuleInferenceFailure 

605 name = module.__name__ 

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

607 frame = frame.f_back 

608 return name 

609 

610 

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

612 parts = name.split( '.' ) 

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

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

615 

616 

617def _merge_ic_configuration( 

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

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

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

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

622 result[ 'flavors' ] = ( 

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

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

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

626 uvalue = update.get( ename ) 

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

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

629 return result 

630 

631 

632def _produce_ic_configuration( 

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

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

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

636 vconfig = vehicle.generalcfg 

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

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

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

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 __.ImmutableDictionary( configd )