Package: instld
INSTLD: the simplest package managementThanks to this package, it is very easy to manage the lifecycle of packages.⚡ Run your code without installing libraries.⚡ You can use 2 different versions of the same library in the same program.⚡ You can use incompatible libraries in the same project, as well as libraries with incompatible/conflicting dependencies.⚡ It's easy to share written scripts. The script file becomes self-sufficient - the user does not need to install the necessary libraries.⚡ The library does not leave behind "garbage". After the end of the program, no additional files remain in the system.Table of contentsQuick startREPL modeScript launch modeContext manager modeInstalling multiple packagesOptionsUsing an existing virtual environmentOutput and loggingSpecial comment languageUsing multiple environmentsHow does it work?Quick startInstallit:pipinstallinstldAnd use the library in one of three ways: by typing commands via REPL, by running your script through it or by importing a context manager from there.If you run the scriptlike this, all dependencies will be automatically installed when the application starts and deleted when it stops:instldscript.pyTheREPL modeworks in a similar way, you just need to typeinstldin the console to enter it.You can also call thecontext managerfrom your code:importinstldwithinstld('some_package'):importsome_moduleRead more about each method, its capabilities and limitations below.REPL modeREPL mode is the fastest and easiest way to try out other people's libraries for your code. Just type this in your console:instldAfter that you will see a welcome message similar to this:⚡ INSTLD REPL based on
Python 3.11.6 (main, Oct  2 2023, 13:45:54) [Clang 15.0.0 (clang-1500.0.40.1)] on darwin
Type "help", "copyright", "credits" or "license" for more information.

>>>Enjoy the regular Pythoninteractive console mode! Any libraries that you ask for will be installed within the session, and after exiting it, they will be deleted without a trace. You don't need to "clean up" anything after exiting the console.In this mode, aspecial comment languageis fully supported.Script launch modeYou can useinstldto run your script from a file. To do this, you need to run a command like this in the console:instldscript.pyThe contents of the script will be executed in the same way as if you were running it through thepython script.pycommand. If necessary, you can pass additional arguments to the command line, as if you are running a regular Python script. However, if your program has imports of any packages other than the built-in ones, they will be installed automatically. Installed packages are automatically cleaned up when you exit the program, so they don't leave any garbage behind.In this mode, as inREPL, aspecial comment languageis fully supported.Context manager modeYou can also useinstldto install and use packages in runtime. The context managerinstldgenerates a context. While you are inside the context manager, you can import modules using the usualimportcommand:withinstld('some_package'):importsome_moduleHowever, there are cases when you need the module to be imported strictly from a given context. In this case, it is better to use theimport_heremethod:withinstld('some_package')ascontext:module=context.import_here('some_module')The library provides isolation of various contexts among themselves, so in the second case, the module will be imported strictly from the context that you need.⚠️  Some modules use lazy imports. If such an import happens after exiting the context manager, it will break your program. Please make sure that all the internal components of the libraries used have been initialized before the execution of your code goes out of context.Installing multiple packagesYou can install several packages by specifying their names separated by commas:withinstld('package_1','package_2','package_3')ascontext:module_1=context.import_here('module_1')module_2=context.import_here('module_2')module_3=context.import_here('module_3')In this case, all packages will be installed in one context and you can import them all from there.You can also create separate contexts for different packages:withinstld('package_1')ascontext_1:withinstld('package_2')ascontext_2:withinstld('package_3')ascontext_3:module_1=context_1.import_here('module_1')module_2=context_2.import_here('module_2')module_3=context_3.import_here('module_3')In this case, each package was installed in its own independent context, and we import each module from the context where the corresponding package was installed.This capability is very powerful. You can place libraries in different contexts that are incompatible with each other. You can also install different versions of the same library in neighboring contexts. Here's how it will work using theFlaskexample:withinstld('flask==2.0.2')ascontext_1:withinstld('flask==2.0.0')ascontext_2:flask_1=context_1.import_here('flask')flask_2=context_2.import_here('flask')print(flask_1.__version__)# 2.0.2print(flask_2.__version__)# 2.0.0⚠️  Keep in mind that although inter-thread isolation is used inside the library, working with contexts is not completely thread-safe. You can write code in such a way that two different contexts import different modules in separate threads at the same time. In this case, you may get paradoxical results. Therefore, it is recommended to additionally isolate with mutexes all cases where you import something from contexts in different threads.OptionsYou can useany optionsavailable forpip. To do this, you need to slightly change the name of the option, replacing the hyphens with underscores, and pass it as an argument toinstld. Here is an example of how using the--index-urloption will look like:withinstld('super_test_project==0.0.1',index_url='https://test.pypi.org/simple/'):importsuper_testYou cannot use options that tellpipwhere to install libraries.Using an existing virtual environmentBy default, through thecontext manager, packages are installed in a temporary virtual environment, which is deleted after exiting the context. However, if you want to install the package in a permanent environment, there is also a way to do this: use thewhereargument.withinstld('package',where='path/to/the/venv'):importpackageWhen manually specifying the path to the virtual environment directory, you need to consider several points:The format of the separator differs in different operating systems. For example, in Linux it is/, and in Windows it is\. To make your code multiplatform, useos.path.jointo define the path.You need to make sure that the virtual environment that you are passing the path to is created by the same Python interpreter that you use to run your code. Virtual environments created by different interpreters are not compatible with each other. For the same reasons, it is not worth storing virtual environment files in Git.Output and loggingBy default, you can see the output of the installation progress in the console:>>>withinstld('flask'):...importflask...CollectingflaskUsingcachedFlask-2.3.2-py3-none-any.whl(96kB)Collectingclick>=8.1.3Usingcachedclick-8.1.3-py3-none-any.whl(96kB)Collectingimportlib-metadata>=3.6.0Usingcachedimportlib_metadata-6.6.0-py3-none-any.whl(22kB)CollectingJinja2>=3.1.2UsingcachedJinja2-3.1.2-py3-none-any.whl(133kB)CollectingWerkzeug>=2.3.3UsingcachedWerkzeug-2.3.3-py3-none-any.whl(242kB)Collectingitsdangerous>=2.1.2Usingcacheditsdangerous-2.1.2-py3-none-any.whl(15kB)Collectingblinker>=1.6.2Usingcachedblinker-1.6.2-py3-none-any.whl(13kB)Collectingzipp>=0.5Usingcachedzipp-3.15.0-py3-none-any.whl(6.8kB)CollectingMarkupSafe>=2.0UsingcachedMarkupSafe-2.1.2-cp39-cp39-macosx_10_9_universal2.whl(17kB)Installingcollectedpackages:zipp,MarkupSafe,Werkzeug,Jinja2,itsdangerous,importlib-metadata,click,blinker,flaskSuccessfullyinstalledJinja2-3.1.2MarkupSafe-2.1.2Werkzeug-2.3.3blinker-1.6.2click-8.1.3flask-2.3.2importlib-metadata-6.6.0itsdangerous-2.1.2zipp-3.15.0If you don't want to see this output, pass thecatch_outputargument:>>>withinstld('flask',catch_output=True):...importflask...>>>In case of installation errors, you will get aninstld.errors.InstallingPackageErrorexception. From the object of this exception, you can getstdoutandstderreven if you have forbidden the output:frominstld.errorsimportInstallingPackageErrortry:withinstld('some_wrong_pack',catch_output=True):importsome_wrong_moduleexceptInstallingPackageErrorase:print(e.stdout)print(e.stderr)Logging is also enabled by default for installing packages. You can see it if you configure logging correctly. In this case:importlogginglogging.basicConfig(level=logging.DEBUG,format="%(asctime)s[%(levelname)s]%(message)s",handlers=[logging.StreamHandler(),])withinstld('flask',catch_output=True):importflask... the logs will look something like this:2023-05-02 13:47:56,752 [INFO] The beginning of the execution of the command "/Users/pomponchik/Desktop/Projects/magic-action-runner/venv/bin/python3 -m venv /var/folders/54/p5qzzp9j65zckq9kd2k31t9c0000gn/T/tmpiajesk4s/venv".
2023-05-02 13:47:58,993 [INFO] The command "/Users/pomponchik/Desktop/Projects/magic-action-runner/venv/bin/python3 -m venv /var/folders/54/p5qzzp9j65zckq9kd2k31t9c0000gn/T/tmpiajesk4s/venv" has been executed.
2023-05-02 13:47:58,993 [INFO] The beginning of the execution of the command "/Users/pomponchik/Desktop/Projects/magic-action-runner/venv/bin/python3 -m pip install --target=/var/folders/54/p5qzzp9j65zckq9kd2k31t9c0000gn/T/tmpiajesk4s/venv/lib/python3.9/site-packages flask".
2023-05-02 13:48:01,052 [INFO] The command "/Users/pomponchik/Desktop/Projects/magic-action-runner/venv/bin/python3 -m pip install --target=/var/folders/54/p5qzzp9j65zckq9kd2k31t9c0000gn/T/tmpiajesk4s/venv/lib/python3.9/site-packages flask" has been executed.TheINFOlevelis used by default. For errors -ERROR.Special comment languageWhen using script launch or REPL mode, you can specify additional parameters for each import inside your program. To do this, you need to write immediately after it (but always in the same line!) a comment that starts with "instld:", separating key and value pairs with commas.As example, if the name of the imported module and the package name are different, this code imports theffunction from thefazylibrary version0.0.3:importf# instld: version 0.0.3, package fazyprint(f('some string'))You can also specify only the version or only the package name in the comment, they do not have to be specified together.Using multiple environmentsThe instld script launch mode and REPL mode provides a unique opportunity to use multiple virtual environments at the same time.Firstly, you can run scripts in the main virtual environment, and it will work exactly as you expect:python3-mvenvvenvsourcevenv/bin/activate
instldscript.pyWhen the "import" command is executed in your script, the package will first be searched in the activated virtual environment, and only then downloaded if it is not found there. Note that by default, the activated virtual environment is read-only. That is, it is assumed that you will install all the necessary libraries there before running your script. If you want to install packages in runtime in a specific virtual environment - read about the second method further.Secondly, you can specify the path to the virtual environment directlyin the commentsto a specific import using thewheredirective:importsomething# instld: where path/to/the/venvIf the path you specified does not exist when you first run the script, it will be automatically created. Libraries installed in this way are not deleted when the script is stopped, therefore, starting from the second launch, the download is no longer required.Note that the path to the virtual environment in this case should not contain spaces. In addition, there is no multiplatform way to specify directory paths using a comment. Therefore, it is not recommended to use paths consisting of more than one part.Since script launch mode uses a context manager to install packages "under the hood", you should also read about the features of installing packages in this way in thecorresponding section.How does it work?This package is essentially a wrapper forvenvandpip.When entering the context, a temporary folder is created using thetempfilelibrary. Then it is added tosys.path, and after exiting the context, it is removed from there. To install the package in this particular temporary folder, the--targetargument is passed to pip, indicating the path to it. Interaction withpipandvenvoccurs throughsubprocesses.Theimport_heremethod works by temporarily substitutingsys.pathandsys.modules. This is necessary so that the search for packages takes place only in the necessary directories.