# -*- coding: utf-8 -*-""":class:`Module`, :class:`Package` data model."""frompathlib_mateimportPathfromcollectionsimportOrderedDictfrom.helpersimportSP_DIR,assert_is_valid_nameTab=" "*4
[docs]classBaseModuleOrPackage:""" Base Class to represent a module or package. """def__init__(self,name,path=None,parent=None,is_single_file=None):assert_is_valid_name(name)self.name=nameself.parent=parentself.is_single_file=is_single_filedefread_sp_dir(p:Path)->str:""" Read the site-packages directory from a .egg-link or .pth file. """withopen(p.abspath,"rb")asf:returnf.readline().decode("utf-8").strip()ifpathisNone:chain=self.name.split(".")# "a.b.c" -> ["a", "b", "c"]root=chain[0]# take the first part of the full module import path# test if using .egg-link or using .pthp_egg_link_1=Path(SP_DIR,root.replace("_","-")+".egg-link")p_egg_link_2=Path(SP_DIR,root.replace("_","_")+".egg-link")p_pth_1=Path(SP_DIR,root.replace("_","-")+".pth")p_pth_2=Path(SP_DIR,root.replace("_","_")+".pth")ifp_egg_link_1.exists()andp_egg_link_1.is_file():sp_dir=read_sp_dir(p_egg_link_1)elifp_egg_link_2.exists()andp_egg_link_2.is_file():sp_dir=read_sp_dir(p_egg_link_2)elifp_pth_1.exists()andp_pth_1.is_file():sp_dir=read_sp_dir(p_pth_1)elifp_pth_2.exists()andp_pth_2.is_file():sp_dir=read_sp_dir(p_pth_2)else:sp_dir=SP_DIR# is single file packagep=Path(Path(sp_dir,*chain).abspath+".py")ifp.is_file()andp.exists():self.path=pself.is_single_file=Truereturn# then has to be a directory having __init__.py filep=Path(sp_dir,*chain)ifp.is_dir()andp.exists()andPath(p,"__init__.py").exists():self.path=Path(sp_dir,*chain)self.is_single_file=FalsereturnraiseValueError("Can't found '%s'!"%self.name)else:self.path=path@propertydeffullname(self):""" Example: for package ``pip.commands.install``, it's ``pip.commands.install``. """returnself.name@propertydefshortname(self):""" Example: for package ``pip.commands.install``, it's ``install``. """if"."inself.name:returnself.name.split(".")[-1]else:returnself.namedef__eq__(self,other):returnself.path==other.path
[docs]classModule(BaseModuleOrPackage):""" Represent a module object in Python. Typically it's a ``*.py`` file. :param name: module name, e.g.: "pip.commands.install". :param path: module file absolute path. :param parent: default None, parent package name, list of package :param is_single_file: if it is a single file package/module. """def__repr__(self):return"Module(name=%r, path='%s')"%(self.name,self.path)
[docs]classPackage(BaseModuleOrPackage):""" Represent a package object in Python. It is a directory having a ``__init__.py`` file. :param name: dot seperated full name, e.g.: "pip.commands.install". :param path: package directory/file absolute path. :param parent parent: parent package, instance of :class:`Package`. **中文文档** 是Python中Package概念的抽象类。指包含有 ``__init__.py`` 文件的文件夹。 Package必须可以被import命令所导入, 换言之, 就是已经被成功安装了。 Package的属性的解释: - name: 包名称 - path: 包目录所在的路径 - fullname: 包的全名, 带母包 - shortname: 包的短名称, 也就是最后一个点之后的部分。 - parent: 母包的实例。 - is_single_file: 是否是单文件的包。 - sub_packages: 有序字典, {子包的名称: Package对象} - sub_modules: 有序字典, {子模块的名称: Module对象} """def__init__(self,name,path=None,parent=None,is_single_file=None):super(Package,self).__init__(name,path=path,parent=parent,is_single_file=is_single_file,)self.sub_packages=OrderedDict()self.sub_modules=OrderedDict()# walk through all sub packages and sub modulesifself.is_single_fileisFalse:forpinPath.sort_by_abspath(self.path.iterdir()):# if it's a directoryifp.is_dir():# if there is a __init__.py file, must be a sub packageifPath(p,"__init__.py").exists():pkg=Package(name=name+"."+p.basename,path=p,parent=self,is_single_file=False,)self.sub_packages[p.basename]=pkg# if it's a fileelse:# if it's a .py file, must be a moduleifp.ext==".py"andp.fname!="__init__":module=Module(name=name+"."+p.fname,path=p,parent=self,is_single_file=True,)self.sub_modules[p.fname]=moduledef__str__(self):tpl=("Package(""\n{tab}name=%r,""\n{tab}path='%s',""\n{tab}sub_packages=%r,""\n{tab}sub_modules=%r,""\n)").format(tab=Tab)s=tpl%(self.name,self.path,list(self.sub_packages),list(self.sub_modules),)returnsdef__repr__(self):return"Package(name=%r, path='%s')"%(self.name,self.path)def__getitem__(self,name):if"."inname:item=selffor_nameinname.split("."):item=item[_name]returnitemelse:try:returnself.sub_packages[name]exceptKeyError:try:returnself.sub_modules[name]exceptKeyError:raiseKeyError("%r doesn't has sub module %r!"%(self.name,name))
[docs]defwalk(self,pkg_only=True):""" A generator that walking through all sub packages and sub modules. :type pkg_only: bool :param pkg_only: if True, it only yields package (folder with __init__.py) if False, it also yields module, but they don't have sub_packages and sub_modules **中文文档** 遍历一个包的所有子包以及子模块. 1. current package object (包对象) 2. current package's parent (当前包对象的母包) 3. list of sub packages (所有子包) 4. list of sub modules (所有模块) """current_module=selfparent_module=self.parentsub_packages=list(self.sub_packages.values())sub_modules=list(self.sub_modules.values())yield(current_module,parent_module,sub_packages,sub_modules,)forpkginself.sub_packages.values():forthingsinpkg.walk(pkg_only=pkg_only):yieldthingsifpkg_onlyisFalse:forsub_moduleinself.sub_modules.values():yieldsub_module,self,[],[]
def_tree_view_builder(self,indent=0,is_root=True):""" Build a text to represent the package structure. """defpad_text(indent):return" "*indent+"|-- "lines=list()ifis_root:lines.append(SP_DIR)lines.append("%s%s (%s)"%(pad_text(indent),self.shortname,self.fullname))indent+=1# sub packagesforpkginself.sub_packages.values():lines.append(pkg._tree_view_builder(indent=indent,is_root=False))# __init__.pylines.append("%s%s (%s)"%(pad_text(indent),"__init__.py",self.fullname,))# sub modulesformodinself.sub_modules.values():lines.append("%s%s (%s)"%(pad_text(indent),mod.shortname+".py",mod.fullname,))return"\n".join(lines)
[docs]defpprint(self):""" Pretty print the package structure. """print(self._tree_view_builder(indent=0,is_root=True))