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
« 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 -*-
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#============================================================================#
21''' Protocols for renderable objects. '''
24from . import __
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. '''
34 def render_as_dictionary( self ) -> dict[ str, __.typx.Any ]:
35 ''' Returns dictionary suitable for JSON/TOML serialization. '''
36 return render_as_dictionary( self )
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. '''
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.
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 )