Coverage for sources/ictr/renderables.py: 84%

34 statements  

« prev     ^ index     » next       coverage.py v7.16.1, created at 2026-09-26 12:43 +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''' Protocols for renderable objects. ''' 

22 

23 

24from . import __ 

25 

26 

27@__.typx.runtime_checkable 

28class Renderable( 

29 __.immut.Protocol, __.typx.Protocol, 

30 class_mutables = __.PROTOCOL_RTC_MUTABLES, 

31): 

32 ''' Objects which can be rendered into a dictionary. ''' 

33 

34 def render_as_dictionary( self ) -> dict[ str, __.typx.Any ]: 

35 ''' Returns dictionary suitable for JSON/TOML serialization. ''' 

36 return render_as_dictionary( self ) 

37 

38 

39@__.typx.runtime_checkable 

40class RenderableDataclass( 

41 Renderable, __.immut.DataclassProtocol, __.typx.Protocol, 

42 class_mutables = __.PROTOCOL_RTC_MUTABLES, 

43): 

44 ''' Dataclass objects which can be rendered into a dictionary. ''' 

45 

46 

47def render_as_dictionary( entity: object ) -> dict[ str, __.typx.Any ]: 

48 ''' Returns dictionary suitable for JSON/TOML serialization. ''' 

49 if __.dcls.is_dataclass( entity ) and not isinstance( entity, type ): 

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

51 for field in __.dcls.fields( entity ): 

52 if field.name.startswith( '_' ): continue 

53 value = getattr( entity, field.name ) 

54 result[ field.name ] = _serialize_value( value ) 

55 return result 

56 if hasattr( entity, '__dict__' ): 56 ↛ 62line 56 didn't jump to line 62 because the condition on line 56 was always true

57 result = { } 

58 for name, value in entity.__dict__.items( ): 

59 if name.startswith( '_' ): continue 59 ↛ 58line 59 didn't jump to line 58 because the continue on line 59 wasn't executed

60 result[ name ] = _serialize_value( value ) 

61 return result 

62 raise NotImplementedError # TODO: More specific error class. 

63 

64 

65def _serialize_value( value: __.typx.Any ) -> __.typx.Any: 

66 ''' Recursively serializes a value for dictionary representation. ''' 

67 if isinstance( value, ( str, int, float, bool, __.types.NoneType ) ): 

68 return value 

69 if isinstance( value, __.cabc.Sequence ): 69 ↛ 70line 69 didn't jump to line 70 because the condition on line 69 was never true

70 return [ _serialize_value( e ) for e in value ] # pyright: ignore 

71 if isinstance( value, __.cabc.Mapping ): 71 ↛ 72line 71 didn't jump to line 72 because the condition on line 71 was never true

72 return { 

73 str( k ): _serialize_value( v ) # pyright: ignore 

74 for k, v in value.items( ) } # pyright: ignore 

75 if callable( getattr( value, 'render_as_dictionary', None ) ): 

76 return value.render_as_dictionary( ) 

77 if __.dcls.is_dataclass( value ) and not isinstance( value, type ): 77 ↛ 78line 77 didn't jump to line 78 because the condition on line 77 was never true

78 return render_as_dictionary( value ) 

79 return repr( value )