11import typing
22from collections .abc import Iterable
33from pathlib import PurePath
4+ from typing import Self
45
56
67class ModulePath :
78 _SEP = '.'
89
9- def __init__ (self , module : str | Iterable [str ]):
10+ def __init__ (self , module : str | Iterable [str ], is_module : bool = True ):
1011 if isinstance (module , str ):
1112 module = module .strip ()
12- if module == '' or module .strip () != module :
13- raise ValueError ()
13+ if module .strip () != module :
14+ raise ValueError (f'" { module } "' )
1415 parts : Iterable [str ] = module .split (ModulePath ._SEP )
1516 else :
1617 parts = module
@@ -22,19 +23,22 @@ def __init__(self, module: str | Iterable[str]):
2223 else :
2324 raise ValueError (module )
2425
25- def to_path (self , root : PurePath | None = None , is_module = True ):
26- path = (root or PurePath ()).joinpath (* self .parts )
27- if is_module :
28- name = self .parts [- 1 ]
29- dot_idx = name .rfind ('.' )
30- suffix = name [dot_idx :] if dot_idx != - 1 else '.py'
31- path = path .with_suffix (suffix )
32- return path
26+ self ._is_module = is_module
27+
28+ def to_path (self , root : PurePath | None = None ):
29+ parts = list (self .parts )
30+ if not self ._is_module :
31+ parts .append ('__init__' )
32+ return (root or PurePath ()).joinpath (* parts )
3333
3434 def parent (self ) -> typing .Self | None :
3535 if len (self .parts ) == 1 :
3636 return None
37- return ModulePath (self .parts [:- 1 ])
37+ return ModulePath (self .parts [:- 1 ], False )
38+
39+ @classmethod
40+ def root (cls ) -> Self :
41+ return cls ('' , is_module = False )
3842
3943 def __truediv__ (self , other : str | Iterable [str ]):
4044 if isinstance (other , str ):
@@ -51,3 +55,12 @@ def __eq__(self, other: object):
5155
5256 def __hash__ (self ) -> int :
5357 return hash (self .__str__ ())
58+
59+ def __matmul__ (self , other ):
60+ if not isinstance (other , ModulePath ):
61+ return NotImplemented
62+
63+ if self .parts [: len (other .parts )] != other .parts :
64+ raise ValueError ('Not related' )
65+
66+ return ModulePath (self .parts [len (other .parts ) :])
0 commit comments