Coverage for sources/emcdproj/__/distribution.py: 95%

34 statements  

« prev     ^ index     » next       coverage.py v7.8.2, created at 2025-05-29 22:13 +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''' Information about package distribution. ''' 

22 

23 

24from . import imports as __ 

25# from . import io as _io 

26 

27 

28class Information( metaclass = __.ImmutableDataclass ): 

29 ''' Information about a package distribution. ''' 

30 

31 name: str 

32 location: __.Path 

33 editable: bool 

34 

35 @classmethod 

36 async def prepare( 

37 selfclass, package: str, exits: __.ctxl.AsyncExitStack, 

38 project_anchor: __.Absential[ __.Path ] = __.absent, 

39 ) -> __.typx.Self: 

40 ''' Acquires information about our package distribution. ''' 

41 import sys 

42 # Detect PyInstaller bundle. 

43 if getattr( sys, 'frozen', False ) and hasattr( sys, '_MEIPASS' ): 43 ↛ 44line 43 didn't jump to line 44 because the condition on line 43 was never true

44 project_anchor = __.Path( getattr( sys, '_MEIPASS' ) ) 

45 # TODO: Python 3.12: importlib.metadata 

46 from importlib_metadata import packages_distributions 

47 # https://github.com/pypa/packaging-problems/issues/609 

48 name = packages_distributions( ).get( package ) 

49 if name is None: # Development sources rather than distribution. 

50 editable = True # Implies no use of importlib.resources. 

51 location, name = ( 

52 await _acquire_development_information( 

53 location = project_anchor ) ) 

54 else: 

55 editable = False 

56 name = name[ 0 ] 

57 location = await _acquire_production_location( package, exits ) 

58 return selfclass( 

59 editable = editable, location = location, name = name ) 

60 

61 def provide_data_location( self, *appendages: str ) -> __.Path: 

62 ''' Provides location of distribution data. ''' 

63 base = self.location / 'data' 

64 if appendages: return base.joinpath( *appendages ) 

65 return base 

66 

67 

68async def _acquire_development_information( 

69 location: __.Absential[ __.Path ] = __.absent 

70) -> tuple[ __.Path, str ]: 

71 from tomli import loads 

72 if __.is_absent( location ): 

73 location = __.Path( __file__ ).parents[ 3 ].resolve( strict = True ) 

74 # pyproject = await _io.acquire_text_file_async( 

75 # location / 'pyproject.toml', deserializer = loads ) 

76 with ( location / 'pyproject.toml' ).open( ) as file: 

77 pyproject = loads( file.read( ) ) 

78 name = pyproject[ 'project' ][ 'name' ] 

79 return location, name 

80 

81 

82async def _acquire_production_location( 

83 package: str, exits: __.ctxl.AsyncExitStack 

84) -> __.Path: 

85 # TODO: Python 3.12: importlib.resources 

86 # TODO: 'importlib_resources' PR to fix 'as_file' type signature. 

87 from importlib_resources import files, as_file # pyright: ignore 

88 # Extract package contents to temporary directory, if necessary. 

89 return exits.enter_context( 

90 as_file( files( package ) ) ) # pyright: ignore