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

69 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-10-20 18:40 +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 ) -> tuple[ __.InventoryObject, ... ]: 

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

53 objects = await filter_inventory( 

54 source, filters = filters ) 

55 return tuple( objects ) 

56 

57 

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

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

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

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

62 return base_url._replace( path = new_path ) 

63 

64 

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

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

67 url = derive_inventory_url( base_url ) 

68 url_s = url.geturl( ) 

69 nomargs: __.NominativeArguments = { } 

70 match url.scheme: 

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

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

73 case _: 

74 raise __.InventoryUrlNoSupport( 

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

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

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

78 raise __.InventoryInaccessibility( 

79 url_s, cause = exc ) from exc 

80 except Exception as exc: 

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

82 

83 

84async def filter_inventory( 

85 source: str, /, *, 

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

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

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

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

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

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

92 base_url = __.normalize_base_url( source ) 

93 inventory = extract_inventory( base_url ) 

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

95 for objct in inventory.objects: 

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

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

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

99 continue 

100 obj = format_inventory_object( 

101 objct, inventory, source ) 

102 all_objects.append( obj ) 

103 return tuple( all_objects ) 

104 

105 

106class SphinxInventoryObject( __.InventoryObject ): 

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

108 

109 def render_specifics_markdown( 

110 self, /, *, 

111 reveal_internals: bool = False, 

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

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

114 lines: list[ str ] = [ ] 

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

116 if role: 

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

118 if reveal_internals: 

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

120 if domain: 

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

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

123 if priority is not None: 

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

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

126 if project: 

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

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

129 if version: 

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

131 return tuple( lines ) 

132 

133 def render_specifics_json( 

134 self, /, *, 

135 reveal_internals: bool = False, 

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

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

138 base_data = { 'role': self.specifics.get( 'role' ) } 

139 if reveal_internals: 

140 base_data.update( { 

141 'domain': self.specifics.get( 'domain' ), 

142 'priority': self.specifics.get( 'priority' ), 

143 'inventory_project': self.specifics.get( 'inventory_project' ), 

144 'inventory_version': self.specifics.get( 'inventory_version' ), 

145 } ) 

146 return __.immut.Dictionary( base_data ) 

147 

148 

149def format_inventory_object( 

150 objct: __.typx.Any, 

151 inventory: __.typx.Any, 

152 location_url: str, 

153) -> SphinxInventoryObject: 

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

155 return SphinxInventoryObject( 

156 name = objct.name, 

157 uri = objct.uri, 

158 inventory_type = 'sphinx', 

159 location_url = location_url, 

160 display_name = ( 

161 objct.dispname 

162 if objct.dispname != '-' 

163 else None ), 

164 specifics = __.immut.Dictionary( 

165 domain = objct.domain, 

166 role = objct.role, 

167 priority = objct.priority, 

168 inventory_project = inventory.project, 

169 inventory_version = inventory.version ) )