Coverage for sources/dynadoc/context.py: 100%
85 statements
« prev ^ index » next coverage.py v7.10.1, created at 2025-07-29 05:16 +0000
« prev ^ index » next coverage.py v7.10.1, created at 2025-07-29 05:16 +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''' Data transfer objects for execution context. '''
24from . import __
25from . import interfaces as _interfaces
26from . import nomina as _nomina
29fragments_name_default = '_dynadoc_fragments_'
30introspection_limit_name_default = '_dynadoc_introspection_limit_'
33@__.dcls.dataclass( frozen = True, kw_only = True, slots = True )
34class Context:
36 _dynadoc_fragments_: __.typx.ClassVar[
37 _interfaces.Fragments ] = ( 'context', )
39 notifier: __.typx.Annotated[
40 _interfaces.Notifier,
41 _interfaces.Fname( 'notifier' ),
42 ]
43 fragment_rectifier: __.typx.Annotated[
44 _interfaces.FragmentRectifier,
45 _interfaces.Fname( 'fragment rectifier' ),
46 ]
47 visibility_decider: __.typx.Annotated[
48 _interfaces.VisibilityDecider,
49 _interfaces.Fname( 'visibility decider' ),
50 ]
51 fragments_name: __.typx.Annotated[
52 str,
53 _interfaces.Fname( 'fragments name' ),
54 ] = fragments_name_default
55 introspection_limit_name: __.typx.Annotated[
56 str,
57 _interfaces.Fname( 'introspection limit name' ),
58 ] = introspection_limit_name_default
59 invoker_globals: __.typx.Annotated[
60 __.typx.Optional[ _nomina.Variables ],
61 _interfaces.Fname( 'invoker globals' ),
62 ] = None
63 resolver_globals: __.typx.Annotated[
64 __.typx.Optional[ _nomina.Variables ],
65 _interfaces.Fname( 'resolver globals' ),
66 ] = None
67 resolver_locals: __.typx.Annotated[
68 __.typx.Optional[ _nomina.Variables ],
69 _interfaces.Fname( 'resolver locals' ),
70 ] = None
72 def with_invoker_globals(
73 self,
74 level: _interfaces.GlobalsLevelArgument = 2
75 ) -> __.typx.Self:
76 ''' Returns new context with invoker globals from stack frame. '''
77 iglobals = __.inspect.stack( )[ level ].frame.f_globals
78 return type( self )(
79 notifier = self.notifier,
80 fragment_rectifier = self.fragment_rectifier,
81 visibility_decider = self.visibility_decider,
82 fragments_name = self.fragments_name,
83 introspection_limit_name = self.introspection_limit_name,
84 invoker_globals = iglobals,
85 resolver_globals = self.resolver_globals,
86 resolver_locals = self.resolver_locals )
89ContextArgument: __.typx.TypeAlias = __.typx.Annotated[
90 Context, _interfaces.Fname( 'context' ) ]
91IntrospectionArgumentFref: __.typx.TypeAlias = __.typx.Annotated[
92 'IntrospectionControl', _interfaces.Fname( 'introspection' ) ]
95class ClassIntrospector( __.typx.Protocol ):
96 ''' Custom introspector for class annotations and attributes. '''
98 @staticmethod
99 def __call__( # noqa: PLR0913
100 possessor: _interfaces.PossessorClassArgument, /,
101 context: ContextArgument,
102 introspection: IntrospectionArgumentFref,
103 annotations: _interfaces.AnnotationsArgument,
104 cache: _interfaces.AnnotationsCacheArgument,
105 table: _interfaces.FragmentsTableArgument,
106 ) -> __.typx.Optional[ _interfaces.Informations ]:
107 ''' Introspects class and returns information about its members. '''
108 raise NotImplementedError # pragma: no cover
110ClassIntrospectors: __.typx.TypeAlias = __.cabc.Sequence[ ClassIntrospector ]
113@__.dcls.dataclass( frozen = True, kw_only = True, slots = True )
114class ClassIntrospectionLimit:
115 ''' Limits on class introspection behavior. '''
117 avoid_inheritance: __.typx.Annotated[
118 bool,
119 _interfaces.Doc( ''' Avoid introspecting inherited members? ''' ),
120 ] = False
121 ignore_attributes: __.typx.Annotated[
122 bool,
123 _interfaces.Doc(
124 ''' Ignore attributes not covered by annotations? ''' ),
125 ] = False
128@__.dcls.dataclass( frozen = True, kw_only = True, slots = True )
129class ClassIntrospectionControl:
130 ''' Controls on class introspection behavior. '''
132 inheritance: __.typx.Annotated[
133 bool, _interfaces.Doc( ''' Inherit annotations? ''' )
134 ] = False
135 introspectors: __.typx.Annotated[
136 ClassIntrospectors,
137 _interfaces.Doc( ''' Custom introspectors to apply. ''' ),
138 ] = ( )
139 scan_attributes: __.typx.Annotated[
140 bool,
141 _interfaces.Doc( ''' Scan attributes not covered by annotations? ''' ),
142 ] = False
144 def with_limit(
145 self,
146 limit: __.typx.Annotated[
147 ClassIntrospectionLimit,
148 _interfaces.Doc(
149 ''' Limits to apply to this introspection control. ''' ),
150 ]
151 ) -> __.typx.Self:
152 ''' Returns new control with applied limits. '''
153 inheritance = self.inheritance and not limit.avoid_inheritance
154 scan_attributes = self.scan_attributes and not limit.ignore_attributes
155 return type( self )(
156 inheritance = inheritance,
157 introspectors = self.introspectors,
158 scan_attributes = scan_attributes )
161@__.dcls.dataclass( frozen = True, kw_only = True, slots = True )
162class ModuleIntrospectionLimit:
163 ''' Limits on module introspection behavior. '''
165 ignore_attributes: __.typx.Annotated[
166 bool,
167 _interfaces.Doc(
168 ''' Ignore attributes not covered by annotations? ''' ),
169 ] = False
172@__.dcls.dataclass( frozen = True, kw_only = True, slots = True )
173class ModuleIntrospectionControl:
174 ''' Controls on module introspection behavior. '''
176 scan_attributes: __.typx.Annotated[
177 bool,
178 _interfaces.Doc( ''' Scan attributes not covered by annotations? ''' ),
179 ] = False
181 def with_limit(
182 self,
183 limit: __.typx.Annotated[
184 ModuleIntrospectionLimit,
185 _interfaces.Doc(
186 ''' Limits to apply to this introspection control. ''' ),
187 ]
188 ) -> __.typx.Self:
189 ''' Returns new control with applied limits. '''
190 scan_attributes = self.scan_attributes and not limit.ignore_attributes
191 return type( self )( scan_attributes = scan_attributes )
194class IntrospectionLimiter( __.typx.Protocol ):
195 ''' Can return modified introspection control for attribute. '''
197 @staticmethod
198 def __call__(
199 objct: __.typx.Annotated[
200 object,
201 _interfaces.Doc(
202 ''' Object being evaluated for introspection limits. ''' ),
203 ],
204 introspection: IntrospectionArgumentFref,
205 ) -> 'IntrospectionControl':
206 ''' Returns modified introspection control with limits applied. '''
207 raise NotImplementedError # pragma: no cover
209IntrospectionLimiters: __.typx.TypeAlias = __.typx.Annotated[
210 __.cabc.Sequence[ IntrospectionLimiter ],
211 _interfaces.Doc(
212 ''' Functions which can apply limits to introspection control. ''' ),
213]
216class IntrospectionTargets( __.enum.IntFlag ):
217 ''' Kinds of objects to recursively document. '''
219 Null = 0
220 Class = __.enum.auto( )
221 Descriptor = __.enum.auto( )
222 Function = __.enum.auto( )
223 Module = __.enum.auto( )
226IntrospectionTargetsSansModule: __.typx.Annotated[
227 IntrospectionTargets,
228 _interfaces.Doc( ''' All introspection targets except modules. ''' ),
229] = ( IntrospectionTargets.Class
230 | IntrospectionTargets.Descriptor
231 | IntrospectionTargets.Function )
232IntrospectionTargetsOmni: __.typx.Annotated[
233 IntrospectionTargets,
234 _interfaces.Doc(
235 ''' All available introspection targets including modules. ''' ),
236] = IntrospectionTargetsSansModule | IntrospectionTargets.Module
239@__.dcls.dataclass( frozen = True, kw_only = True, slots = True )
240class IntrospectionLimit:
241 ''' Limits on introspection behavior. '''
243 disable: __.typx.Annotated[
244 bool, _interfaces.Doc( ''' Disable introspection? ''' )
245 ] = False
246 class_limit: __.typx.Annotated[
247 ClassIntrospectionLimit,
248 _interfaces.Doc( ''' Limits specific to class introspection. ''' ),
249 ] = ClassIntrospectionLimit( )
250 module_limit: __.typx.Annotated[
251 ModuleIntrospectionLimit,
252 _interfaces.Doc( ''' Limits specific to module introspection. ''' ),
253 ] = ModuleIntrospectionLimit( )
254 targets_exclusions: __.typx.Annotated[
255 IntrospectionTargets,
256 _interfaces.Doc( ''' Target types to exclude from introspection. ''' ),
257 ] = IntrospectionTargets.Null
260@__.dcls.dataclass( frozen = True, kw_only = True, slots = True )
261class IntrospectionControl:
263 _dynadoc_fragments_ = ( 'introspection', )
265 enable: __.typx.Annotated[
266 bool,
267 _interfaces.Doc( ''' Whether introspection is enabled at all. ''' ),
268 ] = True
269 class_control: __.typx.Annotated[
270 ClassIntrospectionControl,
271 _interfaces.Doc( ''' Controls specific to class introspection. ''' ),
272 ] = ClassIntrospectionControl( )
273 module_control: __.typx.Annotated[
274 ModuleIntrospectionControl,
275 _interfaces.Doc( ''' Controls specific to module introspection. ''' ),
276 ] = ModuleIntrospectionControl( )
277 limiters: __.typx.Annotated[
278 IntrospectionLimiters,
279 _interfaces.Doc(
280 ''' Functions that can apply limits to introspection. ''' ),
281 ] = ( )
282 targets: __.typx.Annotated[
283 IntrospectionTargets,
284 _interfaces.Doc(
285 ''' Which types of objects to recursively document. ''' ),
286 ] = IntrospectionTargets.Null
287 # TODO? Maximum depth.
288 # (Suggested by multiple LLMs; not convinced that it is needed.)
290 def evaluate_limits_for(
291 self,
292 objct: __.typx.Annotated[
293 object,
294 _interfaces.Doc( ''' Object to evaluate limits for. ''' ),
295 ]
296 ) -> 'IntrospectionControl':
297 ''' Determine which introspection limits apply to object. '''
298 introspection_ = self
299 for limiter in self.limiters:
300 introspection_ = limiter( objct, introspection_ )
301 return introspection_
303 def with_limit(
304 self,
305 limit: __.typx.Annotated[
306 IntrospectionLimit,
307 _interfaces.Doc(
308 ''' Limits to apply to this introspection control. ''' ),
309 ]
310 ) -> __.typx.Self:
311 ''' Returns new control with applied limits. '''
312 enable = self.enable and not limit.disable
313 class_control = self.class_control.with_limit( limit.class_limit )
314 module_control = self.module_control.with_limit( limit.module_limit )
315 targets = self.targets & ~limit.targets_exclusions
316 return type( self )(
317 enable = enable,
318 class_control = class_control,
319 module_control = module_control,
320 limiters = self.limiters,
321 targets = targets )
324IntrospectionArgument: __.typx.TypeAlias = __.typx.Annotated[
325 IntrospectionControl, _interfaces.Fname( 'introspection' ) ]