Skip to main content

The simplest package management in runtime

Project description

logo

INSTLD: the simplest package management in runtime

Downloads Downloads codecov Test-Package Python versions PyPI version

Thanks to this package, it is very easy to manage the lifecycle of packages directly from the code. In runtime.

  • ⚡ 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 contents

Quick start

Install it:

pip install instld

And use as in this example:

import installed


with installed('some_package'):
    import some_module

The above code downloads some_package and imports some_module from it.

Imports

The context manager installed generates a context. While you are inside the context manager, you can import modules using the usual import command:

with installed('some_package'):
    import some_module

However, there are cases when you need the module to be imported strictly from a given context. In this case, it is better to use the import_here method:

with installed('some_package') as context:
    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.

Installing multiple packages

You can install several packages by specifying their names separated by commas:

with installed('package_1', 'package_2', 'package_3') as context:
    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:

with installed('package_1') as context_1:
    with installed('package_2') as context_2:
        with installed('package_3') as context_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 the Flask example:

with installed('flask==2.0.2') as context_1:
    with installed('flask==2.0.0') as context_2:
        flask_1 = context_1.import_here('flask')
        flask_2 = context_2.import_here('flask')

        print(flask_1.__version__)  # 2.0.2
        print(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.

Options

You can use any options available for pip. To do this, you need to slightly change the name of the option, replacing the hyphens with underscores, and pass it as an argument to installed. Here is an example of how using the --index-url option will look like:

with installed('super_test_project==0.0.1', index_url='https://test.pypi.org/simple/'):
    import super_test

You cannot use options that tell pip where to install libraries.

Output and logging

By default, you can see the output of the installation progress in the console:

>>> with installed('flask'):
...     import flask
...
Collecting flask
  Using cached Flask-2.3.2-py3-none-any.whl (96 kB)
Collecting click>=8.1.3
  Using cached click-8.1.3-py3-none-any.whl (96 kB)
Collecting importlib-metadata>=3.6.0
  Using cached importlib_metadata-6.6.0-py3-none-any.whl (22 kB)
Collecting Jinja2>=3.1.2
  Using cached Jinja2-3.1.2-py3-none-any.whl (133 kB)
Collecting Werkzeug>=2.3.3
  Using cached Werkzeug-2.3.3-py3-none-any.whl (242 kB)
Collecting itsdangerous>=2.1.2
  Using cached itsdangerous-2.1.2-py3-none-any.whl (15 kB)
Collecting blinker>=1.6.2
  Using cached blinker-1.6.2-py3-none-any.whl (13 kB)
Collecting zipp>=0.5
  Using cached zipp-3.15.0-py3-none-any.whl (6.8 kB)
Collecting MarkupSafe>=2.0
  Using cached MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_universal2.whl (17 kB)
Installing collected packages: zipp, MarkupSafe, Werkzeug, Jinja2, itsdangerous, importlib-metadata, click, blinker, flask
Successfully installed Jinja2-3.1.2 MarkupSafe-2.1.2 Werkzeug-2.3.3 blinker-1.6.2 click-8.1.3 flask-2.3.2 importlib-metadata-6.6.0 itsdangerous-2.1.2 zipp-3.15.0

If you don't want to see this output, pass the catch_output argument:

>>> with installed('flask', catch_output=True):
...     import flask
...
>>>

In case of installation errors, you will get an installed.errors.InstallingPackageError exception. From the object of this exception, you can get stdout and stderr even if you have forbidden the output:

from installed.errors import InstallingPackageError


try:
    with installed('some_wrong_pack', catch_output=True):
        import some_wrong_module
except InstallingPackageError as e:
    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:

import logging


logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s [%(levelname)s] %(message)s",
    handlers=[
        logging.StreamHandler(),
    ]
)

with installed('flask', catch_output=True):
    import flask

... 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.

The INFO level is used by default. For errors - ERROR.

How does it work?

This package is essentially a wrapper for venv and pip.

When entering the context, a temporary folder is created using the tempfile library. Then it is added to sys.path, and after exiting the context, it is removed from there. To install the package in this particular temporary folder, the --target argument is passed to pip, indicating the path to it. Interaction with pip and venv occurs through subprocesses.

The import_here method works by temporarily substituting sys.path and sys.modules. This is necessary so that the search for packages takes place only in the necessary directories.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

instld-0.0.14b0.tar.gz (12.8 kB view details)

Uploaded Source

Built Distributions

instld-0.0.14b0-py3.9.egg (10.7 kB view details)

Uploaded Egg

instld-0.0.14b0-py3-none-any.whl (11.7 kB view details)

Uploaded Python 3

File details

Details for the file instld-0.0.14b0.tar.gz.

File metadata

  • Download URL: instld-0.0.14b0.tar.gz
  • Upload date:
  • Size: 12.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.9.6

File hashes

Hashes for instld-0.0.14b0.tar.gz
Algorithm Hash digest
SHA256 8e4861415450e4a4e23d3e54600a456c558437a7026cbe67811233f0f378af4a
MD5 b6798bb1a4e5c147f67d6016f2657114
BLAKE2b-256 ada2d75d41ce94e7f3543ac9e0729320d99846f233599f31d13b20c580d3e643

See more details on using hashes here.

File details

Details for the file instld-0.0.14b0-py3.9.egg.

File metadata

  • Download URL: instld-0.0.14b0-py3.9.egg
  • Upload date:
  • Size: 10.7 kB
  • Tags: Egg
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.9.6

File hashes

Hashes for instld-0.0.14b0-py3.9.egg
Algorithm Hash digest
SHA256 7d21e498aa69d52fde2146691569befce207f3356d51cf6b6987133aee306bf2
MD5 bcc6861d441b78ce87c68bb2ee9b0ad0
BLAKE2b-256 1ffcf748cfd5c80c26352ac36ede38cc18fe241adcb3378c2934c9d2ba8b6f88

See more details on using hashes here.

File details

Details for the file instld-0.0.14b0-py3-none-any.whl.

File metadata

  • Download URL: instld-0.0.14b0-py3-none-any.whl
  • Upload date:
  • Size: 11.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.9.6

File hashes

Hashes for instld-0.0.14b0-py3-none-any.whl
Algorithm Hash digest
SHA256 f4863dd49aac6cc4f5568186b8a360a20dcdc67e954bf0907e74f2a5776105b9
MD5 aa9df4cbb9929ae3aaf44b9f120a9295
BLAKE2b-256 c644b73456ad70a6dcbb18b0a58ec4fc15ab03baec7e585fd618bc7deaa8067f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page