55import base64
66import json
77import os
8+ import pkgutil
89import sys
910import types
1011import zlib
1314from importlib .metadata import Distribution
1415from importlib .metadata import DistributionFinder
1516from typing import IO
16- from typing import TYPE_CHECKING
1717from typing import Any
1818from typing import Iterable
19+ from typing import NamedTuple
20+ from typing import NewType
21+ from typing import Protocol
1922from typing import Sequence
23+ from typing import cast
24+ from typing import runtime_checkable
2025
21- if TYPE_CHECKING :
22- pass
26+ ModuleName = NewType ("ModuleName" , str )
2327
2428
2529class DictDistribution (Distribution ):
@@ -35,56 +39,58 @@ def locate_file(self, path: str | os.PathLike[str]) -> os.PathLike[str]:
3539 raise FileNotFoundError (path )
3640
3741
42+ class ModuleInfo (NamedTuple ):
43+ name : ModuleName
44+ is_pkg : bool
45+ source : str
46+
47+
3848class DictImporter (DistributionFinder , Loader ):
3949 """a limited loader/importer for distributins send via json-lines"""
4050
41- def __init__ (self , sources : dict [str , str ], distribution : DictDistribution ):
51+ def __init__ (
52+ self , sources : dict [ModuleName , ModuleInfo ], distribution : DictDistribution
53+ ):
4254 self .sources = sources
4355 self .distribution = distribution
4456
4557 def find_distributions (
46- self , context : DistributionFinder .Context = DistributionFinder . Context ()
58+ self , context : DistributionFinder .Context | None = None
4759 ) -> Iterable [Distribution ]:
60+ # TODO: filter
4861 return [self .distribution ]
4962
5063 def find_module (
5164 self , fullname : str , path : Sequence [str | bytes ] | None = None
5265 ) -> Loader | None :
53- if fullname in self .sources :
54- return self
55- if fullname + ".__init__" in self .sources :
66+ if ModuleName (fullname ) in self .sources :
5667 return self
5768 return None
5869
59- def load_module (self , fullname ) :
70+ def load_module (self , fullname : str ) -> types . ModuleType :
6071 # print "load_module:", fullname
61- from types import ModuleType
6272
63- try :
64- s = self .sources [fullname ]
65- is_pkg = False
66- except KeyError :
67- s = self .sources [fullname + ".__init__" ]
68- is_pkg = True
73+ info = self .sources [ModuleName (fullname )]
6974
70- co = compile (s , fullname , "exec" )
71- module = sys .modules .setdefault (fullname , ModuleType (fullname ))
75+ co = compile (info . source , fullname , "exec" )
76+ module = sys .modules .setdefault (fullname , types . ModuleType (fullname ))
7277 module .__loader__ = self
73- if is_pkg :
78+ if info . is_pkg :
7479 module .__path__ = [fullname ]
7580
7681 exec (co , module .__dict__ )
7782 return sys .modules [fullname ]
7883
7984 def get_source (self , name : str ) -> str | None :
80- res = self .sources .get (name )
85+ res = self .sources .get (ModuleName ( name ) )
8186 if res is None :
82- res = self .sources .get (name + ".__init__" )
83- return res
87+ return None
88+ else :
89+ return res .source
8490
8591
8692def bootstrap (
87- modules : dict [str , str ],
93+ modules : dict [ModuleName , ModuleInfo ],
8894 distribution : dict [str , str ],
8995 entry : str ,
9096 args : dict [str , Any ],
@@ -100,7 +106,7 @@ def bootstrap(
100106 entry_func (** args )
101107
102108
103- def bootstrap_stdin (stream : IO ) -> None :
109+ def bootstrap_stdin (stream : IO [ bytes ] | IO [ str ] ) -> None :
104110 bootstrap_args = decode_b85_zip_json (stream .readline ())
105111 bootstrap (** bootstrap_args )
106112
@@ -111,9 +117,26 @@ def decode_b85_zip_json(encoded: bytes | str):
111117 return json .loads (unpacked )
112118
113119
114- def naive_pack_module (module : types .ModuleType , dist : Distribution ):
120+ @runtime_checkable
121+ class SourceProvidingLoader (Protocol ):
122+ def get_source (self , name : str ) -> str :
123+ ...
124+
125+
126+ def naive_pack_module (module : types .ModuleType , dist : Distribution ) -> object :
115127 assert module .__file__ is not None
116128 assert module .__path__
129+ data : dict [ModuleName , ModuleInfo ] = {}
130+ for info in pkgutil .walk_packages (module .__path__ , f"{ module .__name__ } ." ):
131+ spec = info .module_finder .find_spec (info .name , None )
132+ assert spec is not None
133+ loader = cast (SourceProvidingLoader , spec .loader )
134+
135+ source = loader .get_source (info .name )
136+ data [ModuleName (info .name )] = ModuleInfo (
137+ name = ModuleName (info .name ), is_pkg = info .ispkg , source = source
138+ )
139+ return data
117140
118141
119142if __name__ == "__main__" :
0 commit comments