In Go we have ability to import modules from github, like:
import"github.com/parnurzeal/gorequest"
It's a bit controversial feature, but sometimes it's useful. And I was interested, is it possible to implement something like that in python. TLDR it's possible with import_from_github_com package:
fromgithub_com.kennethreitzimportrequestsassertrequests.get('https://github.com').status_code==200
So, how it works, according to PEP-0302
we have special sys.meta_path
with importer objects and every importer
should implement finder protocol with find_module(module_name: str, package_path: [str]) -> Loader|None
.
Now we need to implement finder that handles modules,
which path starts with github_com
, like:
classGithubComFinder:deffind_module(self,module_name,package_path):ifmodule_name.startswith('github_com'):returnGithubComLoader()sys.meta_path.append(GithubComFinder())
And now we need GithubComLoader
that implements loader protocol with load_module(fullname: str) -> None
,
I'll skip private methods of the loader here, they're straightforward and not interesting in
context of the article:
classGithubComLoader:defload_module(self,fullname):ifself._is_repository_path(fullname):self._install_module(fullname)ifself._is_intermediate_path(fullname):module=IntermediateModule(fullname)else:module=self._import_module(fullname)sys.modules[fullname]=module
So what's IntermediateModule
, it's a dummy module/package for paths like github_com.nvbn
,
it's used only in intermediate steps and shouldn't be used by end user. Installation happens in
_install_module
method, it just calls pip
with git url, like:
importpippip.main(['install','git+https://github.com/kennethreitz/requests'])
All looks very simple, let's try it in action:
>>>fromgithub_com.kennethreitzimportrequestsCollectinggit+https://github.com/kennethreitz/requestsCloninghttps://github.com/kennethreitz/requeststo/tmp/pip-8yyvh7kr-buildInstallingcollectedpackages:requestsRunningsetup.pyinstallforrequestsSuccessfullyinstalledrequests-2.9.1>>>requests.get('https://github.com').status_code200