Coverage for sources/librovore/inventories/sphinx/detection.py: 16%

66 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-09-02 00:02 +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''' Inventory detection implementations. ''' 

22 

23 

24from urllib.parse import ParseResult as _Url 

25 

26import sphobjinv as _sphobjinv 

27 

28from . import __ 

29 

30 

31class SphinxInventoryDetection( __.InventoryDetection ): 

32 ''' Detection result for Sphinx inventory sources. ''' 

33 

34 @classmethod 

35 async def from_source( 

36 selfclass, 

37 auxdata: __.ApplicationGlobals, 

38 processor: __.Processor, 

39 source: str, 

40 ) -> __.typx.Self: 

41 ''' Constructs Sphinx inventory detection from source. ''' 

42 # TODO: Figure out why this is not used. 

43 # This is not used in current implementation 

44 return selfclass( processor = processor, confidence = 0.0 ) 

45 

46 async def filter_inventory( 

47 self, 

48 auxdata: __.ApplicationGlobals, 

49 source: str, /, *, 

50 filters: __.cabc.Mapping[ str, __.typx.Any ], 

51 details: __.InventoryQueryDetails = ( 

52 __.InventoryQueryDetails.Documentation ), 

53 ) -> tuple[ __.InventoryObject, ... ]: 

54 ''' Filters inventory objects from Sphinx source. ''' 

55 objects = await filter_inventory( 

56 source, filters = filters, details = details ) 

57 return tuple( objects ) 

58 

59 

60def derive_inventory_url( base_url: _Url ) -> _Url: 

61 ''' Derives objects.inv URL from base URL ParseResult. ''' 

62 new_path = f"{base_url.path}/objects.inv" 

63 # TODO: Do not rely on named tuple internals. 

64 return base_url._replace( path = new_path ) 

65 

66 

67def extract_inventory( base_url: _Url ) -> _sphobjinv.Inventory: 

68 ''' Extracts and parses Sphinx inventory from URL or file path. ''' 

69 url = derive_inventory_url( base_url ) 

70 url_s = url.geturl( ) 

71 nomargs: __.NominativeArguments = { } 

72 match url.scheme: 

73 case 'http' | 'https': nomargs[ 'url' ] = url_s 

74 case 'file': nomargs[ 'fname_zlib' ] = url.path 

75 case _: 

76 raise __.InventoryUrlNoSupport( 

77 url, component = 'scheme', value = url.scheme ) 

78 try: return _sphobjinv.Inventory( **nomargs ) 

79 except ( ConnectionError, OSError, TimeoutError ) as exc: 

80 raise __.InventoryInaccessibility( 

81 url_s, cause = exc ) from exc 

82 except Exception as exc: 

83 raise __.InventoryInvalidity( url_s, cause = exc ) from exc 

84 

85 

86async def filter_inventory( 

87 source: str, /, *, 

88 filters: __.cabc.Mapping[ str, __.typx.Any ], 

89 details: __.InventoryQueryDetails = ( 

90 __.InventoryQueryDetails.Documentation ), 

91) -> tuple[ __.InventoryObject, ... ]: 

92 ''' Extracts and filters inventory objects by structural criteria only. ''' 

93 domain = filters.get( 'domain', '' ) or __.absent 

94 role = filters.get( 'role', '' ) or __.absent 

95 priority = filters.get( 'priority', '' ) or __.absent 

96 base_url = __.normalize_base_url( source ) 

97 inventory = extract_inventory( base_url ) 

98 all_objects: list[ __.InventoryObject ] = [ ] 

99 for objct in inventory.objects: 

100 if not __.is_absent( domain ) and objct.domain != domain: continue 

101 if not __.is_absent( role ) and objct.role != role: continue 

102 if not __.is_absent( priority ) and objct.priority != priority: 

103 continue 

104 obj = format_inventory_object( 

105 objct, inventory, source ) 

106 all_objects.append( obj ) 

107 return tuple( all_objects ) 

108 

109 

110class SphinxInventoryObject( __.InventoryObject ): 

111 ''' Sphinx-specific inventory object with domain-aware formatting. ''' 

112 

113 def render_specifics_markdown( 

114 self, /, *, 

115 reveal_internals: bool = True, 

116 ) -> tuple[ str, ... ]: 

117 ''' Renders Sphinx specifics with domain and role information. ''' 

118 lines: list[ str ] = [ ] 

119 role = self.specifics.get( 'role' ) 

120 if role: 

121 lines.append( f"- **Type:** {role}" ) 

122 domain = self.specifics.get( 'domain' ) 

123 if domain: 

124 lines.append( f"- **Domain:** {domain}" ) 

125 if reveal_internals: 

126 priority = self.specifics.get( 'priority' ) 

127 if priority is not None: 

128 lines.append( f"- **Priority:** {priority}" ) 

129 project = self.specifics.get( 'inventory_project' ) 

130 if project: 

131 lines.append( f"- **Project:** {project}" ) 

132 version = self.specifics.get( 'inventory_version' ) 

133 if version: 

134 lines.append( f"- **Version:** {version}" ) 

135 return tuple( lines ) 

136 

137 def render_specifics_json( 

138 self 

139 ) -> __.immut.Dictionary[ str, __.typx.Any ]: 

140 ''' Renders Sphinx specifics with structured format information. ''' 

141 return __.immut.Dictionary( 

142 role = self.specifics.get( 'role' ), 

143 domain = self.specifics.get( 'domain' ), 

144 priority = self.specifics.get( 'priority' ), 

145 inventory_project = self.specifics.get( 'inventory_project' ), 

146 inventory_version = self.specifics.get( 'inventory_version' ), 

147 ) 

148 

149 

150def format_inventory_object( 

151 objct: __.typx.Any, 

152 inventory: __.typx.Any, 

153 location_url: str, 

154) -> SphinxInventoryObject: 

155 ''' Formats Sphinx inventory object with complete attribution. ''' 

156 return SphinxInventoryObject( 

157 name = objct.name, 

158 uri = objct.uri, 

159 inventory_type = 'sphinx_objects_inv', 

160 location_url = location_url, 

161 display_name = ( 

162 objct.dispname 

163 if objct.dispname != '-' 

164 else None ), 

165 specifics = __.immut.Dictionary( 

166 domain = objct.domain, 

167 role = objct.role, 

168 priority = objct.priority, 

169 inventory_project = inventory.project, 

170 inventory_version = inventory.version ) )