{"id":15014964,"url":"https://github.com/contextlab/davos","last_synced_at":"2026-03-07T14:31:45.645Z","repository":{"id":44860522,"uuid":"343938262","full_name":"ContextLab/davos","owner":"ContextLab","description":"Import packages in Python, even if they aren't installed!","archived":false,"fork":false,"pushed_at":"2023-10-27T04:22:56.000Z","size":37975,"stargazers_count":35,"open_issues_count":15,"forks_count":8,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-04-08T13:48:11.984Z","etag":null,"topics":["environment-management","google-colab","import","install","ipython","jupyter","package-management","pip","python","reproducibility"],"latest_commit_sha":null,"homepage":"","language":"TeX","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ContextLab.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":"CITATION.cff","codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2021-03-02T23:08:46.000Z","updated_at":"2025-01-16T16:30:27.000Z","dependencies_parsed_at":"2023-01-23T19:30:58.180Z","dependency_job_id":"aba2ccb6-1a9c-46aa-87fc-0846e5f020e4","html_url":"https://github.com/ContextLab/davos","commit_stats":{"total_commits":1048,"total_committers":7,"mean_commits":"149.71428571428572","dds":"0.10209923664122134","last_synced_commit":"0608a40b25995b07e9edf4b9e0098b675187a7aa"},"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ContextLab%2Fdavos","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ContextLab%2Fdavos/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ContextLab%2Fdavos/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ContextLab%2Fdavos/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ContextLab","download_url":"https://codeload.github.com/ContextLab/davos/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248096220,"owners_count":21047004,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["environment-management","google-colab","import","install","ipython","jupyter","package-management","pip","python","reproducibility"],"created_at":"2024-09-24T19:46:20.029Z","updated_at":"2026-03-07T14:31:45.589Z","avatar_url":"https://github.com/ContextLab.png","language":"TeX","readme":"\u003cdiv align=\"center\"\u003e\n  \u003ch1\u003edavos\u003c/h1\u003e\n  \u003cimg src=\"https://user-images.githubusercontent.com/26118297/116332586-0c6ce080-a7a0-11eb-94ad-0502c96cf8ef.png\" width=250/\u003e\n  \u003cbr\u003e\n  \u003cbr\u003e\n  \u003ca href=\"https://github.com/ContextLab/davos/actions/workflows/ci-tests-jupyter.yml\"\u003e\n    \u003cimg src=\"https://github.com/ContextLab/davos/actions/workflows/ci-tests-jupyter.yml/badge.svg?branch=main\" alt=\"CI Tests (Jupyter)\"\u003e\n  \u003c/a\u003e\n  \u003c!-- \u003ca href=\"https://github.com/ContextLab/davos/actions/workflows/ci-tests-colab.yml\"\u003e\n    \u003cimg src=\"https://github.com/ContextLab/davos/actions/workflows/ci-tests-colab.yml/badge.svg?branch=main\u0026event=push\" alt=\"CI Tests (Colab)\"\u003e\n  \u003c/a\u003e --\u003e\n  \u003ca href=\"https://pypi.org/project/davos/\"\u003e\n    \u003cimg src=\"https://img.shields.io/pypi/pyversions/davos?logo=python\u0026logoColor=white\" alt=\"Python Versions\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://pepy.tech/project/davos\"\u003e\n    \u003cimg src=\"https://static.pepy.tech/personalized-badge/davos?period=total\u0026units=international_system\u0026left_color=grey\u0026right_color=blue\u0026left_text=Downloads\" alt=\"PyPI Downloads\"\u003e\n  \u003c/a\u003e\n  \u003cbr\u003e\n  \u003ca href=\"https://arxiv.org/abs/2211.15445\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/arXiv-2211.15445-b31b1b\" alt=\"link to arXiv paper\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://www.codefactor.io/repository/github/contextlab/davos\"\u003e\n    \u003cimg src=\"https://img.shields.io/codefactor/grade/github/ContextLab/davos/main?logo=codefactor\u0026logoColor=brightgreen\" alt=\"code quality (CodeFactor)\"\u003e\n  \u003c/a\u003e\n  \u003cimg src=\"https://img.shields.io/badge/mypy-type%20checked-blue\" alt=\"mypy: checked\"\u003e\n  \u003cbr\u003e\n  \u003ca href=\"https://github.com/ContextLab/davos/blob/main/LICENSE\"\u003e\n    \u003cimg src=\"https://img.shields.io/github/license/ContextLab/davos\" alt=\"License: MIT\"\u003e\n  \u003c/a\u003e\n  \u003cbr\u003e\n  \u003cbr\u003e\n\u003c/div\u003e\n\n\u003e _Someone once told me that the night is dark and full of terrors. And tonight I am no knight. Tonight I am Davos the\nsmuggler again. Would that you were an onion._\n\u003cdiv align=\"right\"\u003e\n  \u0026mdash;\u003ca href=\"https://gameofthrones.fandom.com/wiki/Davos_Seaworth\"\u003eSer Davos Seaworth\u003c/a\u003e\n  \u003cbr\u003e\n  \u003ca href=\"https://en.wikipedia.org/wiki/A_Song_of_Ice_and_Fire\"\u003e\u003ci\u003eA Clash of Kings\u003c/i\u003e\u003c/a\u003e by\n  \u003ca href=\"https://en.wikipedia.org/wiki/George_R._R._Martin\"\u003eGeorge R. R. Martin\u003c/a\u003e\n  \u003cbr\u003e\n\u003c/div\u003e\n\n# Introduction\n\nThe `davos` library provides Python with an additional keyword: **`smuggle`**.\n\n[The `smuggle` statement](#the-smuggle-statement) works just like the built-in [`import` statement](https://docs.python.org/3/reference/import.html), with two major differences: \n1. You can `smuggle` a package _without installing it first_\n2. You can `smuggle` a _specific version_ of a package\n\nTaken together, these two enhancements to `import` provide a powerful system for developing and sharing reproducible code that works across different users and environments.\n\n## Table of contents\n- [Table of contents](#table-of-contents)\n- [Introduction (↑)](#introduction)\n  - [Why would I want an alternative to `import`?](#why-would-i-want-an-alternative-to-import)\n  - [Why not use virtual environments, containers, and/or virtual machines instead?](#why-not-use-virtual-environments-containers-andor-virtual-machines-instead)\n  - [Quick start](#okay-so-how-do-i-use-this-thing)\n- [Installation](#installation)\n  - [Latest Stable PyPI Release](#latest-stable-pypi-release)\n  - [Latest GitHub Update](#latest-github-update)\n  - [Installing in Colaboratory](#installing-in-colaboratory)\n- [Overview](#overview)\n  - [Smuggling Missing Packages](#smuggling-missing-packages)\n  - [Smuggling Specific Package Versions](#smuggling-specific-package-versions)\n  - [Use Cases](#use-cases)\n    - [Simplify sharing reproducible code \u0026 Python environments](#simplify-sharing-reproducible-code--python-environments)\n    - [Guarantee your code always uses the latest version, release, or revision](#guarantee-your-code-always-uses-the-latest-version-release-or-revision)\n    - [Compare behavior across package versions](#compare-behavior-across-package-versions)\n- [User guide](#usage)\n  - [The `smuggle` Statement](#the-smuggle-statement)\n    - [Syntax](#smuggle-statement-syntax)\n    - [Rules](#smuggle-statement-rules)\n  - [The Onion Comment](#the-onion-comment)\n    - [Syntax](#onion-comment-syntax)\n    - [Rules](#onion-comment-rules)\n  - [The `davos` Config](#the-davos-config)\n    - [Reference](#config-reference)\n    - [Top-level Functions](#top-level-functions)\n- [How It Works: The `davos` Parser](#how-it-works-the-davos-parser)\n- [Additional Notes](#additional-notes)\n\n## Why would I want an alternative to `import`?\n\nIn many cases, `smuggle` and `import` do the same thing\u0026mdash;*if you're\nrunning code in the same environment you developed it in*. But what if you want\nto share a [Jupyter notebook](https://jupyter.org/) containing your code with\nsomeone else? If the user (i.e., the \"someone else\" in this example) doesn't\nhave all of the packages your notebook imports, Python will raise an exception\nand the code won't run. It's not a huge deal, of course, but it's inconvenient\n(e.g., the user might need to `pip`-install the missing packages, restart their\nkernel, re-run the code up to the point it crashed, etc.\u0026mdash;possibly going\nthrough this cycle multiple times until the thing finally runs).\n\nA second (and more subtle) issue arises when the developer (i.e., the person\nwho *wrote* the code) used or assumed different versions of the imported\npackages than what the user has installed in their environment. So maybe the\noriginal author was developing and testing their code using `pandas` 1.3.5, but\nthe user hasn't upgraded their `pandas` installation since 0.25.0. Python will\nhappily \"`import pandas`\" in both cases, but any changes across those versions\nmight change what the developer's code actually does in the user's (different)\nenvironment\u0026mdash;or cause it to fail altogether.\n\nThe problem `davos` tries to solve is similar to the idea motivating virtual\nenvironments, containers, and virtual machines: we want a way of replicating\nthe original developer's environment on the user's machine, to a sufficiently\ngood approximation that we can be \"reasonably confident\" that the code will\ncontinue to behave as expected.\n\nWhen you `smuggle` packages instead of importing them, it guarantees (for\nwhatever environment the code is running in) that the packages are importable,\neven if they hadn't been installed previously. Under the hood, `davos` figures\nout whether the package is available, and if not, it uses `pip` to download and\ninstall anything that's missing (including missing dependencies). From that\npoint, after having automatically handled those sorts of dependency issues,\n`smuggle` behaves just like `import`.\n\nThe second powerful feature of `davos` comes from another construct, called\n\"[_onion comments_](#the-onion-comment).\" These are like standard Python\ncomments, but they appear on the same line(s) as `smuggle` statements, and they\nare formatted in a particular way. Onion comments provide a way of precisely\ncontrolling how, when, and where packages are installed, how (or if) the system\nchecks for existing installations, and so on. A key feature is the ability to\nspecify exactly which version(s) of each package are imported into the current\nworkspace. When used in this way, `davos` enables authors to guarantee that the\nsame versions of the packages they developed their code with will also be\nimported into the user's workspace at the appropriate times.\n\n## Why not use virtual environments, containers, and/or virtual machines instead?\n\nPsst-- we'll let you in on a little secret: importing `davos` *automatically*\ncreates a virtual environment for your notebook. However, whereas setting up a\nvirtual environment is usually left to the user, `davos` handles the pesky\ndetails for you, without you needing to think about them. Any packages you\n`smuggle` via `davos` that aren't available in the notebook's original runtime\nenvironment are installed into a new virtual environment. This ensures that\n`davos` will not change the runtime environment (e.g., by installing new\npackages, changing existing package versions, etc.).\n\nBy default, each notebook's virtual environment is stored in a hidden \".davos\"\nfolder inside the current user's home directory. The default environment name\nis computed to uniquely identify each notebook, according to its filename and\npath. However, a notebook's virtual environment may be customized by setting\n`davos.project` to any string that can be used as a valid folder name in the\nuser's operating system. This is useful for multi-notebook projects that share\ndependencies (without needing to duplicate each package installation for each\nnotebook).\n\nIf you prefer, you can also disable `davos`'s virtual environment\ninfrastructure by setting `davos.project` to `None`. Doing so will cause any\npackages installed by `davos` to affect the notebook's runtime environment.\nThis is generally not recommended, as it can lead to unintended consequences\nfor other code that shares the runtime environment. That said, `davos` also\nworks great when used inside of (standard) virtual environments, containers,\nand virtual machines.\n\nThere are a few additional specific advantages to `davos` that go beyond more\ntypical virtual environments, containers, and/or virtual machines. The main\nadvantage is that `davos` is very lightweight: importing `davos` into a\nnotebook-based environment unlocks all of its functionality without needed to\ninstall, set up, and learn how to use additional stuff. There is none of the\ntypical overhead of setting up a new virtual environment (or container, virtual\nmachine, etc.), installing third-party tools, writing and sharing configuration\nfiles, and so on. All of your code *and its dependencies* may be contained in a\nsingle notebook file.\n\n## Okay... so how do I use this thing?\n\nTo turn a standard [Jupyter (IPython) notebook](https://jupyter.org/), including a [Google Colaboratory notebook](https://colab.research.google.com), into a `davos`-enhanced notebook, just add two lines to the first cell:\n```python\n%pip install davos\nimport davos\n```\n\nThis will enable the `smuggle` keyword in your notebook environment.  Then you can do things like:\n\n```python\n# pip-install numpy v1.23.1, if needed\nsmuggle numpy as np    # pip: numpy==1.23.1\n\n# the smuggled package is fully imported and usable\narr = np.arange(15).reshape(3, 5)\n\n# and the onion comment guarantees the desired version!\nassert np.__version__ == '1.23.1'\n```\n\nInterested? Curious? Intrigued? Check out the table of contents for more\ndetails! You may also want to check out our [paper](paper/main.pdf) for more\nformal descriptions and explanations.\n\n## Installation\n### Latest Stable PyPI Release\n[![](https://img.shields.io/pypi/v/davos?label=PyPI\u0026logo=pypi)](https://pypi.org/project/davos/)\n[![](https://img.shields.io/pypi/status/davos)]((https://pypi.org/project/davos/))\n[![](https://img.shields.io/pypi/format/davos)]((https://pypi.org/project/davos/))\n```sh\npip install davos\n```\n\n\n### Latest GitHub Update\n[![](https://img.shields.io/github/commits-since/ContextLab/davos/latest)](https://github.com/ContextLab/davos/releases)\n[![](https://img.shields.io/github/last-commit/ContextLab/davos?logo=git\u0026logoColor=white)](https://github.com/ContextLab/davos/commits/main)\n[![](https://img.shields.io/github/release-date/ContextLab/davos?label=last%20release)](https://github.com/ContextLab/davos/releases/latest)\n\n```sh\npip install git+https://github.com/ContextLab/davos.git\n```\n\n\n### Installing in Colaboratory\nTo install `davos` in [Google Colab](https://colab.research.google.com/), add a new cell to the top of your notebook with an\npercentage sign (`%`) followed by one of the commands above (e.g., `%pip install davos`). You'll likely also want to `import davos`,\nwhich enables the `smuggle` syntax.  Run the cell to install `davos` on the runtime virtual machine.\n\n**Note**: restarting the Colab runtime does not affect installed packages. However, if the runtime is \"factory reset\"\nor disconnected due to reaching its idle timeout limit, you'll need to rerun the cell to reinstall `davos` on the fresh\nVM instance.\n\n\n## Overview\nThe primary way to use `davos` is via [the `smuggle` statement](#the-smuggle-statement), which is made available\nsimply by running `import davos`. Like\n[the built-in `import` statement](https://docs.python.org/3/reference/import.html), the `smuggle` statement is used to\nload packages, modules, and other objects into the current namespace. The main difference between the two is in how\nthey handle missing packages and specific package versions.\n\n\n### Smuggling Missing Packages\n`import` requires that packages be installed _before_ the start of the interpreter session. Trying to `import` a package\nthat can't be found locally will throw a\n[`ModuleNotFoundError`](https://docs.python.org/3/library/exceptions.html#ModuleNotFoundError), and you'll have to\ninstall the package from the command line, restart the Python interpreter to make the new package importable, and rerun\nyour code in full in order to use it.\n\n**The `smuggle` statement, however, can handle missing packages on the fly**. If you `smuggle` a package that isn't\ninstalled locally, `davos` will install it for you, make its contents available to Python's\n[import machinery](https://docs.python.org/3/reference/import.html), and load it into the namespace for immediate use.\nYou can control _how_ `davos` installs missing packages by adding a special type of inline comment called an\n[\"onion\" comment](#the-onion-comment) next to a `smuggle` statement.\n\n\n### Smuggling Specific Package Versions\nOne simple but powerful use for [onion comments](#the-onion-comment) is making `smuggle` statements _version-sensitive_.\n\nPython doesn't provide a native, viable way to ensure a third-party package imported at runtime matches a specific\nversion or satisfies a particular [version constraint](https://www.python.org/dev/peps/pep-0440/#version-specifiers).\nMany packages expose their version info via a top-level `__version__` attribute (see\n[PEP 396](https://www.python.org/dev/peps/pep-0396/)), and certain tools (such as the standard library's\n[`importlib.metadata`](https://docs.python.org/3/library/importlib.metadata.html) and\n[`setuptools`](https://setuptools.readthedocs.io/en/latest/index.html)'s\n[`pkg_resources`](https://setuptools.readthedocs.io/en/latest/pkg_resources.html)) attempt to parse version info from\ninstalled distributions. However, using these to constrain imported package would require writing extra code to compare\nversion strings and _still_ manually installing the desired version and restarting the interpreter any time an\ninvalid version is caught.\n\nAdditionally, for packages installed through a version control system (e.g., [git](https://git-scm.com/)), this would be\ninsensitive to differences between revisions (e.g., commits) within the same semantic version.\n\n**`davos` solves these issues** by allowing you to specify a specific version or set of acceptable versions for each\nsmuggled package. To do this, simply provide a\n[version specifier](https://www.python.org/dev/peps/pep-0440/#version-specifiers) in an\n[onion comment](#the-onion-comment) next to the `smuggle` statement:\n```python\nsmuggle numpy as np              # pip: numpy==1.23.1\nfrom pandas smuggle DataFrame    # pip: pandas\u003e=1.0,\u003c2.0\n```\nIn this example, the first line will load [`numpy`](https://numpy.org/) into the local namespace under the alias \"`np`\",\njust as \"`import numpy as np`\" would. First, `davos` will check whether `numpy` is installed locally, and if so, whether\nthe installed version _exactly_ matches `1.23.1`. If `numpy` is not installed, or the installed version is anything\nother than `1.23.1`, `davos` will use the specified _installer program_, [`pip`](https://pip.pypa.io/en/stable/), to\ninstall `numpy==1.23.1` before loading the package.\n\nSimilarly, the second line will load the \"`DataFrame`\" object from the [`pandas`](https://pandas.pydata.org/) library,\nanalogously to \"`from pandas import DataFrame`\". A local `pandas` version of `1.2.1` would be used, but a local version\nof `2.1.1` would cause `davos` to replace it with a valid `pandas` version, as if you had manually run `pip install\npandas\u003e=1.0,\u003c2.0`.\n\nIn both cases, the imported versions will fit the constraints specified in their [onion comments](#the-onion-comment),\nand the next time `numpy` or `pandas` is smuggled with the same constraints, valid local installations will be found.\n\nYou can also force the state of a smuggled packages to match a specific VCS ref (branch, revision, tag, release, etc.).\nFor example:\n```python\nsmuggle hypertools as hyp    # pip: git+https://github.com/ContextLab/hypertools.git@98a3d80\n```\nwill load [`hypertools`](https://hypertools.readthedocs.io/en/latest/) (aliased as \"`hyp`\"), as the package existed\n[on GitHub](https://github.com/ContextLab/hypertools), at commit\n[98a3d80](https://github.com/ContextLab/hypertools/tree/98a3d80). The general format for VCS references in\n[onion comments](#the-onion-comment) follows that of the\n[`pip-install`](https://pip.pypa.io/en/stable/topics/vcs-support) command. See the\n[notes on smuggling from VCS](#notes-vcs-smuggle) below for additional info.\n\nAnd with [a few exceptions](#notes-c-extensions), smuggling a specific package version will work _even if the package\nhas already been imported_!\n\n**Note**: `davos` v0.2.x supports [IPython](https://ipython.readthedocs.io/en/stable/) environments  (e.g.,\n[Jupyter](https://jupyter.org/) and [Colaboratory](https://colab.research.google.com/) notebooks) only. v0.3.x will add\nsupport for \"regular\" (i.e., non-interactive) Python scripts.\n\n\n### Use Cases\n#### Simplify sharing reproducible code \u0026 Python environments\nDifferent versions of the same package can often behave quite differently\u0026mdash;bugs are introduced and fixed, features\nare implemented and removed, support for Python versions is added and dropped, etc. Because of this, Python code that is\nmeant to be _reproducible_ (e.g., tutorials, demos, data analyses) is commonly shared alongside a set of fixed versions\nfor each package used. And since there is no Python-native way to specify package versions at runtime (see\n[above](#smuggling-specific-package-versions)), this typically takes the form of a pre-configured development\nenvironment the end user must build themselves (e.g., a [Docker](https://www.docker.com/) container or\n[conda](https://docs.conda.io/en/latest/) environment), which can be cumbersome, slow to set up, resource-intensive, and\nconfusing for newer users, as well as require shipping both additional specification files _and_ setup instructions\nalong with your code. And even then, a well-intentioned user may alter the environment in a way that affects your\ncarefully curated set of pinned packages (such as installing additional packages that trigger dependency updates).\n\nInstead, `davos` allows you to share code with one simple instruction: _just `pip install davos`!_ Replace your `import`\nstatements with `smuggle` statements, pin package versions in onion comments, and let `davos` take care of the rest.\nBeyond its simplicity, this approach ensures your predetermined package versions are in place every time your code is\nrun.\n\n\n#### Guarantee your code always uses the latest version, release, or revision\nIf you want to make sure you're always using the most recent release of a certain package, `davos` makes doing so easy:\n```python\nsmuggle mypkg    # pip: mypkg --upgrade\n```\nOr if you have an automation designed to test your most recent commit on GitHub:\n```python\nsmuggle mypkg    # pip: git+https://username/reponame.git\n```\n\n\n#### Compare behavior across package versions\nThe ability to `smuggle` a specific package version even after a different version has been imported makes `davos` a\nuseful tool for comparing behavior across multiple versions of the same package, within the same interpreter session:\n```python\ndef test_my_func_unchanged():\n    \"\"\"Regression test for `mypkg.my_func()`\"\"\"\n    data = list(range(10))\n\n    smuggle mypkg                    # pip: mypkg==0.1\n    result1 = mypkg.my_func(data)\n\n    smuggle mypkg                    # pip: mypkg==0.2\n    result2 = mypkg.my_func(data)\n\n    smuggle mypkg                    # pip: git+https://github.com/MyOrg/mypkg.git\n    result3 = mypkg.my_func(data)\n\n    assert result1 == result2 == result3\n```\n\n\n## Usage\n### The `smuggle` Statement\n#### \u003ca name=\"smuggle-statement-syntax\"\u003e\u003c/a\u003eSyntax\nThe `smuggle` statement is meant to be used in place of\n[the built-in `import` statement](https://docs.python.org/3/reference/import.html) and shares\n[its full syntactic definition](https://docs.python.org/3/reference/simple_stmts.html#the-import-statement):\n```ebnf\nsmuggle_stmt    ::=  \"smuggle\" module [\"as\" identifier] (\",\" module [\"as\" identifier])*\n                     | \"from\" relative_module \"smuggle\" identifier [\"as\" identifier]\n                     (\",\" identifier [\"as\" identifier])*\n                     | \"from\" relative_module \"smuggle\" \"(\" identifier [\"as\" identifier]\n                     (\",\" identifier [\"as\" identifier])* [\",\"] \")\"\n                     | \"from\" module \"smuggle\" \"*\"\nmodule          ::=  (identifier \".\")* identifier\nrelative_module ::=  \".\"* module | \".\"+\n```\n\u003csup\u003e\n  \u003ci\u003e\n    NB: uses the modified BNF grammar notation described in\n    \u003ca href=\"https://docs.python.org/3/reference\"\u003eThe Python Language Reference\u003c/a\u003e,\n    \u003ca href=\"https://docs.python.org/3/reference/introduction.html#notation\"\u003ehere\u003c/a\u003e; see\n    \u003ca href=\"https://docs.python.org/3/reference/lexical_analysis.html#identifiers\"\u003ehere\u003c/a\u003e for the lexical definition\n    of \u003ccode\u003eidentifier\u003c/code\u003e\n  \u003c/i\u003e\n\u003c/sup\u003e\n\n\nIn simpler terms, **any valid syntax for `import` is also valid for `smuggle`**.\n\n\n#### \u003ca name=\"smuggle-statement-rules\"\u003e\u003c/a\u003eRules\n- Like `import` statements, `smuggle` statements are whitespace-insensitive, unless a lack of whitespace between two\n  tokens would cause them to be interpreted as a different token:\n  ```python\n  from os.path smuggle dirname, join as opj                       # valid\n  from   os   . path   smuggle  dirname    ,join      as   opj    # also valid\n  from os.path smuggle dirname, join asopj                        # invalid (\"asopj\" != \"as opj\")\n  ```\n- Any context that would cause an `import` statement _not_ to be executed will have the same effect on a `smuggle`\n  statement:\n  ```python\n  # smuggle matplotlib.pyplot as plt           # not executed\n  print('smuggle matplotlib.pyplot as plt')    # not executed\n  foo = \"\"\"\n  smuggle matplotlib.pyplot as plt\"\"\"          # not executed\n  ```\n- Because the `davos` parser is less complex than the full Python parser, there are two fairly non-disruptive edge\n  cases where an `import` statement would be syntactically valid but a `smuggle` statement would not:\n  1. The [exec](https://docs.python.org/3.8/library/functions.html#exec) function\n     ```python\n     exec('from pathlib import Path')         # executed\n     exec('from pathlib smuggle Path')        # raises SyntaxError\n     ```\n  2. A one-line [compound statement](https://docs.python.org/3.9/reference/compound_stmts.html#compound-statements)\n     clause:\n     ```python\n     if True: import random                   # executed\n     if True: smuggle random                  # raises SyntaxError\n\n     while True: import math; break           # executed\n     while True: smuggle math; break          # raises SyntaxError\n\n     for _ in range(1): import json           # executed\n     for _ in range(1): smuggle json          # raises SyntaxError\n\n     # etc...\n     ```\n- In [IPython](https://ipython.readthedocs.io/en/stable/) environments (e.g., [Jupyter](https://jupyter.org/) \u0026\n  [Colaboratory](https://colab.research.google.com/) notebooks) `smuggle` statements always load names into the global\n  namespace:\n  ```python\n  # example.ipynb\n  import davos\n\n\n  def import_example():\n      import datetime\n\n\n  def smuggle_example():\n      smuggle datetime\n\n\n  import_example()\n  type(datetime)                               # raises NameError\n\n  smuggle_example()\n  type(datetime)                               # returns\n  ```\n\n\n### The Onion Comment\nAn _onion comment_ is a special type of inline comment placed on a line containing a `smuggle` statement. Onion comments\ncan be used to control how `davos`:\n1. determines whether the smuggled package should be installed\n2. installs the smuggled package, if necessary\n\nOnion comments are also useful when smuggling a package whose _distribution name_ (i.e., the name\nused when installing it) is different from its _top-level module name_ (i.e., the name used when importing it). Take for\nexample:\n```python\nfrom sklearn.decomposition smuggle pca    # pip: scikit-learn\n```\nThe onion comment here (`# pip: scikit-learn`) tells `davos` that if \"`sklearn`\" does not exist\nlocally, the \"`scikit-learn`\" package should be installed.\n\n#### \u003ca name=\"onion-comment-syntax\"\u003e\u003c/a\u003eSyntax\nOnion comments follow a simple but specific syntax, inspired in part by the\n[type comment syntax](https://www.python.org/dev/peps/pep-0484/#type-comments) introduced in\n[PEP 484](https://www.python.org/dev/peps/pep-0484). The following is a loose (pseudo-)syntactic definition for an onion\ncomment:\n```ebnf\nonion_comment   ::=  \"#\" installer \":\" install_opt* pkg_spec install_opt*\ninstaller       ::=  (\"pip\" | \"conda\")\npkg_spec        ::=  identifier [version_spec]\n```\n\u003csup\u003e\n  \u003ci\u003e\n    NB: uses the modified BNF grammar notation described in\n    \u003ca href=\"https://docs.python.org/3/reference\"\u003eThe Python Language Reference\u003c/a\u003e,\n    \u003ca href=\"https://docs.python.org/3/reference/introduction.html#notation\"\u003ehere\u003c/a\u003e; see\n    \u003ca href=\"https://docs.python.org/3/reference/lexical_analysis.html#identifiers\"\u003ehere\u003c/a\u003e for the lexical definition\n    of \u003ccode\u003eidentifier\u003c/code\u003e\n  \u003c/i\u003e\n\u003c/sup\u003e\n\nwhere `installer` is the program used to install the package; `install_opt` is any option accepted by the installer's\n\"`install`\" command; and `version_spec` may be a\n[version specifier](https://www.python.org/dev/peps/pep-0440/#version-specifiers) defined by\n[PEP 440](https://www.python.org/dev/peps/pep-0440) followed by a\n[version string](https://www.python.org/dev/peps/pep-0440/#public-version-identifiers), or an alternative syntax valid\nfor the given `installer` program. For example, [`pip`](https://pip.pypa.io/en/stable/) uses specific syntaxes for\n[local](https://pip.pypa.io/en/stable/cli/pip_install/#local-project-installs),\n[editable](https://pip.pypa.io/en/stable/cli/pip_install/#editable-installs), and\n[VCS-based](https://pip.pypa.io/en/stable/topics/vcs-support) installation.\n\nLess formally, **an onion comment simply consists of two parts, separated by a colon**:\n1. the name of the installer program (e.g., [`pip`](https://pip.pypa.io/en/stable/))\n2. arguments passed to the program's \"install\" command\n\nThus, you can essentially think of writing an onion comment as taking the full shell command you would run to install\nthe package, and replacing \"_install_\" with \"_:_\". For instance, the command:\n```sh\npip install -I --no-cache-dir numpy==1.23.1 -vvv --timeout 30\n```\nis easily translated into an onion comment as:\n```python\nsmuggle numpy    # pip: -I --no-cache-dir numpy==1.23.1 -vvv --timeout 30\n```\n\nIn practice, onion comments are identified as matches for the\n[regular expression](https://en.wikipedia.org/wiki/Regular_expression):\n```regex\n#+ *(?:pip|conda) *: *[^#\\n ].+?(?= +#| *\\n| *$)\n```\n\u003csup\u003e\n  \u003ci\u003e\n    NB: support for installing \u003ccode\u003esmuggle\u003c/code\u003ed packages via\n    \u003ca href=\"https://docs.conda.io/en/latest/\"\u003e\u003ccode\u003econda\u003c/code\u003e\u003c/a\u003e will be added in v0.2. For v0.1,\n    \"\u003ccode\u003epip\u003c/code\u003e\" should be used exclusively.\n  \u003c/i\u003e\n\u003c/sup\u003e\n\n**Note**: support for installing smuggled packages via the [`conda`](https://docs.conda.io/en/latest/) package manager\nwill be added in v0.2. For v0.1, onion comments should always specify \"`pip`\" as the `installer` program.\n\n\n#### \u003ca name=\"onion-comment-rules\"\u003e\u003c/a\u003eRules\n- An onion comment must be placed on the same line as a `smuggle` statement; otherwise, it is not parsed:\n  ```python\n  # assuming the dateutil package is not installed...\n\n  # pip: python-dateutil                       # \u003c-- has no effect\n  smuggle dateutil                             # raises InstallerError (no \"dateutil\" package exists)\n\n  smuggle dateutil                             # raises InstallerError (no \"dateutil\" package exists)\n  # pip: python-dateutil                       # \u003c-- has no effect\n\n  smuggle dateutil    # pip: python-dateutil   # installs \"python-dateutil\" package, if necessary\n  ```\n- An onion comment may be followed by unrelated inline comments as long as they are separated by at least one space:\n  ```python\n  smuggle tqdm    # pip: tqdm\u003e=4.46,\u003c4.60 # this comment is ignored\n  smuggle tqdm    # pip: tqdm\u003e=4.46,\u003c4.60            # so is this one\n  smuggle tqdm    # pip: tqdm\u003e=4.46,\u003c4.60# but this comment raises OnionArgumentError\n  ```\n- An onion comment must be the first inline comment immediately following a `smuggle` statement; otherwise, it is not\n  parsed:\n  ```python\n  smuggle numpy    # pip: numpy!=1.19.1        # \u003c-- guarantees smuggled version is *not* v1.19.1\n  smuggle numpy    # has no effect --\u003e         # pip: numpy==1.19.1\n  ```\n  This also allows you to easily \"comment out\" onion comments:\n  ```python\n  smuggle numpy    ## pip: numpy!=1.19.1       # \u003c-- has no effect\n  ```\n- Onion comments are generally whitespace-insensitive, but installer arguments must be separated by at least one space:\n  ```python\n  from umap smuggle UMAP    # pip: umap-learn --user -v --no-clean     # valid\n  from umap smuggle UMAP#pip:umap-learn --user     -v    --no-clean    # also valid\n  from umap smuggle UMAP    # pip: umap-learn --user-v--no-clean       # raises OnionArgumentError\n  ```\n- Onion comments have no effect on standard library modules:\n  ```python\n  smuggle threading    # pip: threading==9999  # \u003c-- has no effect\n  ```\n- When smuggling multiple packages with a _single_ `smuggle` statement, an onion comment may be used to refer to the\n  **first** package listed:\n  ```python\n  smuggle nilearn, nibabel, nltools    # pip: nilearn==0.7.1\n  ```\n- If multiple _separate_ `smuggle` statements are placed on a single line, an onion comment may be used to refer to the\n  **last** statement:\n  ```python\n  smuggle gensim; smuggle spacy; smuggle nltk    # pip: nltk~=3.5 --pre\n  ```\n- For multiline `smuggle` statements, an onion comment may be placed on the first line:\n  ```python\n  from scipy.interpolate smuggle (    # pip: scipy==1.6.3\n      interp1d,\n      interpn as interp_ndgrid,\n      LinearNDInterpolator,\n      NearestNDInterpolator,\n  )\n  ```\n  ... or on the last line:\n  ```python\n  from scipy.interpolate smuggle (interp1d,                  # this comment has no effect\n                                  interpn as interp_ndgrid,\n                                  LinearNDInterpolator,\n                                  NearestNDInterpolator)     # pip: scipy==1.6.3\n  ```\n  ... though the first line takes priority:\n  ```python\n  from scipy.interpolate smuggle (    # pip: scipy==1.6.3    # \u003c-- this version is installed\n      interp1d,\n      interpn as interp_ndgrid,\n      LinearNDInterpolator,\n      NearestNDInterpolator,\n  )    # pip: scipy==1.6.2                                   # \u003c-- this comment is ignored\n  ```\n  ... and all comments _not_ on the first or last line are ignored:\n  ```python\n  from scipy.interpolate smuggle (\n      interp1d,                       # pip: scipy==1.6.3    # \u003c-- ignored\n      interpn as interp_ndgrid,\n      LinearNDInterpolator,           # unrelated comment    # \u003c-- ignored\n      NearestNDInterpolator\n  )                                   # pip: scipy==1.6.2    # \u003c-- parsed\n  ```\n- The onion comment is intended to describe how a specific smuggled package should be installed if it is not found\n  locally, in order to make it available for immediate use. Therefore, installer options that either (A) install\n  packages other than the smuggled package and its dependencies (e.g., from a specification file), or (B) cause the\n  smuggled package not to be installed, are disallowed. The options listed below will raise an `OnionArgumentError`:\n  - `-h`, `--help`\n  - `-r`, `--requirement`\n  - `-V`, `--version`\n\n\n### The `davos` Config\nThe `davos` config object stores options and data that affect how `davos` behaves. After importing `davos`, the config\ninstance (a singleton) for the current session is available as `davos.config`, and its various fields are accessible as\nattributes. The config object exposes a mixture of writable and read-only fields. Most `davos.config` attributes can be\nassigned values to control aspects of `davos` behavior, while others are available for inspection but are set and used\ninternally. Additionally, certain config fields may be writable in some situations but not others (e.g. only if the\nimporting environment supports a particular feature). Once set, `davos` config options last for the lifetime of the\ninterpreter (unless updated); however, they do *not* persist across interpreter sessions. A full list of `davos` config\nfields is available [below](#config-reference):\n\n#### \u003ca name=\"config-reference\"\u003e\u003c/a\u003eReference\n| Field | Description | Type | Default | Writable? |\n| :---: | --- | :---: | :---: | :---: |\n| `active` | Whether or not the `davos` parser should be run on subsequent input (cells, in Jupyter/Colab notebooks). Setting to `True` activates the `davos` parser, enables the `smuggle` keyword, and injects the `smuggle()` function into the user namespace. Setting to `False` deactivates the `davos` parser, disables the `smuggle` keyword, and removes \"`smuggle`\" from the user namespace (if it holds a reference to the `smuggle()` function). See [How it Works](#how-it-works) for more info. | `bool` | `True` | ✅ |\n| `auto_rerun` | If `True`, when smuggling a previously-imported package that cannot be reloaded (see [Smuggling packages with C-extensions](#notes-c-extensions)), `davos` will automatically restart the interpreter and rerun all code up to (and including) the current `smuggle` statement. Otherwise, issues a warning and prompts the user with buttons to either restart/rerun or continue running. | `bool` | `False` | ✅ (**Jupyter notebooks only**) |\n| `confirm_install` | Whether or not `davos` should require user confirmation (`[y/n]` input) before installing a smuggled package | `bool` | `False` | ✅ |\n| `environment` | A label describing the environment into which `davos` was running. Checked internally to determine which interchangeable implementation functions are used, whether certain config fields are writable, and various other behaviors | `Literal['Python', 'IPython\u003c7.0', 'IPython\u003e=7.0', 'Colaboratory']` | N/A | ❌ |\n| `ipython_shell` | The global IPython interactive shell instance | [`IPython.core`\u003cbr\u003e`.interactiveshell`\u003cbr\u003e`.InteractiveShell`](https://ipython.readthedocs.io/en/stable/api/generated/IPython.core.interactiveshell.html#IPython.core.interactiveshell.InteractiveShell) | N/A | ❌ |\n| `noninteractive` | Set to `True` to run `davos` in non-interactive mode (all user input and confirmation will be disabled). **NB**:\u003cbr\u003e1. Setting to `True` disables `confirm_install` if previously enabled \u003cbr\u003e2. If `auto_rerun` is `False` in non-interactive mode, `davos` will throw an error if a smuggled package cannot be reloaded | `bool` | `False` | ✅ (**Jupyter notebooks only**) |\n| `pip_executable` | The path to the `pip` executable used to install smuggled packages. Must be a path (`str` or [`pathlib.Path`](https://docs.python.org/3/library/pathlib.html#pathlib.Path)) to a real file. Default is programmatically determined from Python environment; falls back to `sys.executable -m pip` if executable can't be found | `str` | `pip` exe path or `sys.executable -m pip` | ✅ |\n| `smuggled` | A cache of packages smuggled during the current interpreter session. Formatted as a `dict` whose keys are package names and values are the (`.split()` and `';'.join()`ed) onion comments. Implemented this way so that any non-whitespace change to installer arguments  re-installation | `dict[str, str]` | `{}` | ❌ |\n| `suppress_stdout` | If `True`, suppress all unnecessary output issued by both `davos` and the installer program. Useful when smuggling packages that need to install many dependencies and therefore generate extensive output. If the installer program throws an error while output is suppressed, both stdout \u0026 stderr will be shown with the traceback | `bool` | `False` | ✅ |\n\n#### \u003ca name=\"top-level-functions\"\u003e\u003c/a\u003eTop-level Functions\n`davos` also provides a few convenience for reading/setting config values:\n- **`davos.activate()`**\n  Activate the `davos` parser, enable the `smuggle` keyword, and inject the `smuggle()` function into the namespace.\n  Equivalent to setting `davos.config.active = True`. See [How it Works](#how-it-works) for more info.\n\n- **`davos.deactivate()`**\n  Deactivate the `davos` parser, disable the `smuggle` keyword, and remove the name `smuggle` from the namespace if (and\n  only if) it refers to the `smuggle()` function. If `smuggle` has been overwritten with a different value, the variable\n  will not be deleted. Equivalent to setting `davos.config.active = False`. See [How it Works](#how-it-works) for more\n- info.\n\n- **`davos.is_active()`**\n  Return the current value of `davos.config.active`.\n\n- **`davos.configure(**kwargs)`**\n  Set multiple `davos.config` fields at once by passing values as keyword arguments, e.g.:\n  ```python\n  import davos\n  davos.configure(active=False, noninteractive=True, pip_executable='/usr/bin/pip3')\n  ```\n  is equivalent to:\n  ```python\n  import davos\n  davos.active = False\n  davos.noninteractive = True\n  davos.pip_executable = '/usr/bin/pip3'\n  ```\n\n## How It Works: The `davos` Parser\nFunctionally, importing `davos` appears to enable a new Python keyword, \"_`smuggle`_\". However, `davos` doesn't actually\nmodify the rules or [reserved keywords](https://docs.python.org/3/reference/lexical_analysis.html#keywords) used by\nPython's parser and lexical analyzer in order to do so\u0026mdash;in fact, modifying the Python grammar is not possible at\nruntime and would require rebuilding the interpreter. Instead, in [IPython](https://ipython.readthedocs.io/en/stable/)\nenivonments like [Jupyter](https://jupyter.org/) and\n[Colaboratory](https://colab.research.google.com/notebooks/intro.ipynb) notebooks, `davos` implements the `smuggle`\nkeyword via a combination of namespace injections and its own (far simpler) custom parser.\n\nThe `smuggle` keyword can be enabled and disabled at will by \"activating\" and \"deactivating\" `davos` (see the\n[`davos` Config Reference](config-reference) and [Top-level Functions](#top-level-functions), above). When `davos` is\nimported, it is automatically activated by default. Activating `davos` triggers two things:\n1. The _`smuggle()` function_ is injected into the `IPython` user namespace\n2. The _`davos` parser_ is registered as a\n[custom input transformer](https://ipython.readthedocs.io/en/stable/config/inputtransforms.html)\n\nIPython preprocesses all executed code as plain text before it is sent to the Python parser in order to handle\nspecial constructs like [`%magic`](https://ipython.readthedocs.io/en/stable/interactive/magics.html) and\n[`!shell`](https://ipython.readthedocs.io/en/stable/interactive/reference.html#system-shell-access) commands. `davos`\nhooks into this process to transform `smuggle` statements into syntactically valid Python code. The `davos`\nparser uses [this regular expression](https://github.com/ContextLab/davos/blob/main/davos/core/regexps.py) to match each\nline of code containing a `smuggle` statement (and, optionally, an onion comment), extracts information from its text,\nand replaces it with an analogous call to the _`smuggle()` function_. Thus, even though the code visible to the user may\ncontain `smuggle` statements, e.g.:\n```python\nsmuggle numpy as np    # pip: numpy\u003e1.16,\u003c=1.24 -vv\n```\nthe code that is actually executed by the Python interpreter will not:\n```python\nsmuggle(name=\"numpy\", as_=\"np\", installer=\"pip\", args_str=\"\"\"numpy\u003e1.16,\u003c=1.24 -vv\"\"\", installer_kwargs={'editable': False, 'spec': 'numpy\u003e1.16,\u003c=1.24', 'verbosity': 2})\n```\n\nThe `davos` parser can be deactivated at any time, and doing so triggers the opposite actions of activating it:\n1. The name \"`smuggle`\" is deleted from the `IPython` user namespace, *unless it has been overwritten and no longer\n   refers to the `smuggle()` function*\n2. The `davos` parser input transformer is deregistered.\n\n**Note**: in Jupyter and Colaboratory notebooks, IPython parses and transforms all text in a cell before sending it\nto the kernel for execution. This means that importing or activating `davos` will not make the `smuggle` statement\navailable until the _next_ cell, because all lines in the current cell were transformed before the `davos` parser was\nregistered. However, _deactivating_ `davos` disables the `smuggle` statement immediately\u0026mdash;although the `davos`\nparser will have already replaced all `smuggle` statements with `smuggle()` function calls, removing the function from\nthe namespace causes them to throw `NameError`.\n\n\n## Additional Notes\n- \u003ca name=\"notes-reimplement-cli\"\u003e\u003c/a\u003e**Reimplementing installer programs' CLI parsers**\n\n  The `davos` parser extracts info from onion comments by passing them to a (slightly modified) reimplementation of\n  their specified installer program's CLI parser. This is somewhat redundant, since the arguments will eventually be\n  re-parsed by the _actual_ installer program if the package needs to be installed. However, it affords a number of\n  advantages, such as:\n  - detecting errors early during the parser phase, before spending any time running code above the line containing the\n    `smuggle` statement\n  - preventing shell injections in onion comments\u0026mdash;e.g., `#pip: --upgrade numpy \u0026\u0026 rm -rf /` fails due to the\n    `OnionParser`, but would otherwise execute successfully.\n  - allowing certain installer arguments to temporarily influence `davos` behavior while smuggling the current package\n    (see [Installer options that affect `davos` behavior](#notes-installer-opts) below for specific info)\n\n- \u003ca name=\"notes-installer-opts\"\u003e\u003c/a\u003e**Installer options that affect `davos` behavior**\n\n  Passing certain options to the installer program via an [onion comment](#the-onion-comment) will also affect the\n  corresponding `smuggle` statement in a predictable way:\n\n  - [**`--force-reinstall`**](https://pip.pypa.io/en/stable/cli/pip_install/#cmdoption-force-reinstall) |\n    [**`-I`, `--ignore-installed`**](https://pip.pypa.io/en/stable/cli/pip_install/#cmdoption-I) |\n    [**`-U`, `--upgrade`**](https://pip.pypa.io/en/stable/cli/pip_install/#cmdoption-I)\n\n    The package will be installed, even if it exists locally\n\n  - [**`--no-input`**](https://pip.pypa.io/en/stable/cli/pip/#cmdoption-no-input)\n\n    Disables input prompts, analogous to temporarily setting `davos.config.noninteractive` to `True`. Overrides value\n    of `davos.config.confirm_install`.\n\n  - [**`--src \u003cdir\u003e`**](https://pip.pypa.io/en/stable/cli/pip_install/#cmdoption-src) |\n    [**`-t`, `--target \u003cdir\u003e`**](https://pip.pypa.io/en/stable/cli/pip_install/#cmdoption-t)\n\n    Prepends `\u003cdir\u003e` to [`sys.path`](https://docs.python.org/3/library/sys.html#sys.path) if not already present so\n    the package can be imported.\n\n- \u003ca name=\"notes-c-extensions\"\u003e\u003c/a\u003e**Smuggling packages with C-extensions**\n\n  Some Python packages that rely heavily on custom data types implemented via\n  [C-extensions](https://docs.python.org/3.9/extending/extending.html) (e.g., `numpy`, `pandas`) dynamically generate\n  modules defining various C functions and data structures, and link them to the Python interpreter when they are first\n  imported. Depending on how these objects are initialized, they may not be subject to normal garbage collection, and\n  persist despite their reference count dropping to zero. This can lead to unexpected errors when reloading the Python\n  module that creates them, particularly if their dynamically generated source code has been changed (e.g., because the\n  reloaded package is a newer version).\n\n  This can occasionally affect `davos`'s ability to `smuggle` a new version of a package (or dependency) that was\n  previously imported. To handle this, `davos` first checks each package it installs against\n  [`sys.modules`](https://docs.python.org/3.9/library/sys.html#sys.modules). If a different version has already been\n  loaded by the interpreter, `davos` will attempt to replace it with the requested version. If this fails, `davos` will\n  restore the old package version _in memory_, while replacing it with the new package version _on disk_. This allows\n  subsequent code that uses the non-reloadable module to still execute in most cases, while dependency checks for other\n  packages run against the updated version. Then, depending on the value of `davos.config.auto_rerun`, `davos` will\n  either either automatically restart the interpreter to load the updated package, prompt you to do so, or raise an\n  exception.\n\n- \u003ca name=\"notes-from-reload\"\u003e\u003c/a\u003e**_`from` ... `import` ..._ statements and reloading modules**\n\n  The Python docs for [`importlib.reload()`](https://docs.python.org/3/library/importlib.html#importlib.reload) include\n  the following caveat:\n  \u003e If a module imports objects from another module using\n  \u003e [`from`](https://docs.python.org/3/reference/simple_stmts.html#from) …\n  \u003e [`import`](https://docs.python.org/3/reference/simple_stmts.html#import) …, calling\n  \u003e [`reload()`](https://docs.python.org/3/library/importlib.html#importlib.reload) for the other module does\n  \u003e not redefine the objects imported from it — one way around this is to re-execute the `from` statement, another is to\n  \u003e use `import` and qualified names (_module.name_) instead.\n\n  The same applies to smuggling packages or modules from which objects have already been loaded. If object _`name`_ from\n  module _`module`_ was loaded using either _`from module import name`_ or _`from module smuggle name`_, subsequently\n  running _`smuggle module    # pip --upgrade`_ will in fact install and load an upgraded version of _`module`_, but the\n  the _`name`_ object will still be that of the old version! To fix this, you can simply run _`from module smuggle\n  name`_ either instead in lieu of or after _`smuggle module`_.\n\n\n- \u003ca name=\"notes-vcs-smuggle\"\u003e\u003c/a\u003e**Smuggling packages from version control systems**\n\n  The first time during an interpreter session that a given package is installed from a VCS URL, it is assumed not to be\n  present locally, and is therefore freshly installed. `pip` clones non-editable VCS repositories into a temporary\n  directory, runs `setup.py install`, and then immediately deletes them. Since no information is retained about the\n  state of the repository at installation, it is impossible to determine whether an existing package satisfies the state\n  (i.e., branch, tag, commit hash, etc.) requested for smuggled package.\n\n[comment]: \u003c\u003e (- As with _all_ code, you should use caution when running Python code containing `smuggle` statements that was not written by you or someone you know. )\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcontextlab%2Fdavos","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcontextlab%2Fdavos","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcontextlab%2Fdavos/lists"}