Repository: ContextLab/davos (language: python)

Issue:
add "Examples" docs section?
Currently have an empty section for this in the outline, but may not be necessary given the examples throughout the existing docs

was originally thinking this could also show examples of all valid syntaxes for the `smuggle` statement, but since these are just the same as all valid syntaxes for the `import` statement, it might not be worth adding that much bulk to the docs for something that doesn't really describe `davos`-specific behavior


Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index e8cfea7a..c506904b 100644
--- a/README.md
+++ b/README.md
@@ -1,49 +1,75 @@
 <div align="center">
   <h1>davos</h1>
   <img src="https://user-images.githubusercontent.com/26118297/116332586-0c6ce080-a7a0-11eb-94ad-0502c96cf8ef.png" width=250/>
+  <br>
+  <br>
+  <a href="https://github.com/ContextLab/davos/actions/workflows/ci-tests-jupyter.yml">
+    <img src="https://github.com/ContextLab/davos/actions/workflows/ci-tests-jupyter.yml/badge.svg?branch=main" alt="CI Tests (Jupyter)">
+  </a>
+  <a href="https://github.com/ContextLab/davos/actions/workflows/ci-tests-colab.yml">
+    <img src="https://github.com/ContextLab/davos/actions/workflows/ci-tests-colab.yml/badge.svg?branch=main&event=push" alt="CI Tests (Colab)">
+  </a>
+  <img src="https://img.shields.io/codefactor/grade/github/paxtonfitzpatrick/davos/main?logo=codefactor&logoColor=brightgreen" alt="code quality (CodeFactor)">
+  <img src="https://img.shields.io/badge/mypy-type%20checked-blue" alt="mypy: checked">
+  <br>
+  <a href="https://pypi.org/project/davos/">
+    <img src="https://img.shields.io/pypi/pyversions/davos?logo=python&logoColor=white" alt="Python Versions">
+  </a>
+  <a href="https://pepy.tech/project/davos">
+    <img src="https://static.pepy.tech/personalized-badge/davos?period=total&units=international_system&left_color=grey&right_color=blue&left_text=Downloads" alt="PyPI Downloads">
+  </a>
+  <br>
+  <a href="https://github.com/ContextLab/davos/blob/main/LICENSE">
+    <img src="https://img.shields.io/github/license/ContextLab/davos" alt="License: MIT">
+  </a>
+  <br>
+  <br>
 </div>
 
-🟥🟥🟥 add badges 🟥🟥🟥
-
-
 > _Someone once told me that the night is dark and full of terrors. And tonight I am no knight. Tonight I am Davos the 
 smuggler again. Would that you were an onion._
 <div align="right">
-    &mdash;<a href="https://gameofthrones.fandom.com/wiki/Davos_Seaworth">Ser Davos Seaworth</a>
-    <br>
-    <a href="https://en.wikipedia.org/wiki/A_Song_of_Ice_and_Fire"><i>A Clash of Kings</i></a> by
-    <a href="https://en.wikipedia.org/wiki/George_R._R._Martin">George R. R. Martin</a>
+  &mdash;<a href="https://gameofthrones.fandom.com/wiki/Davos_Seaworth">Ser Davos Seaworth</a>
+  <br>
+  <a href="https://en.wikipedia.org/wiki/A_Song_of_Ice_and_Fire"><i>A Clash of Kings</i></a> by
+  <a href="https://en.wikipedia.org/wiki/George_R._R._Martin">George R. R. Martin</a>
+  <br>
+  <br>
 </div>
 
 
 The `davos` library provides Python with an additional keyword: **`smuggle`**. 
 
-[`smuggle` statements](#the-smuggle-statement) work just like standard 
-[`import` statements](https://docs.python.org/3/reference/import.html) with one major addition: _you can `smuggle` a 
-package without installing it first_. A special type of comment (called an ["**onion**"](#the-onion-comment)) can also 
-be added after a `smuggle` statement, allowing you to _`smuggle` specific package versions_ and fully control 
-installation of missing packages.
+[The `smuggle` statement](#the-smuggle-statement) works just like the built-in 
+[`import` statement](https://docs.python.org/3/reference/import.html), with one major difference: **you can `smuggle` a 
+package without installing it first**. A special type of comment called an 
+["_onion comment_"](#the-onion-comment) can also be added to lines containing `smuggle` statements, allowing you to 
+**`smuggle` specific package versions** and **fully control how missing packages are installed**.
 
-To enable the `smuggle` keyword, simply `import davos`:
+**To enable the `smuggle` keyword, simply `import davos`**:
 ```python
 import davos
 
-# pip-install numpy if needed
+# pip-install numpy v1.20.2, if needed
 smuggle numpy as np    # pip: numpy==1.20.2
 
+
 # the smuggled package is fully imported and usable
 arr = np.arange(15).reshape(3, 5)
+
 # and the onion comment guarantees the desired version!
 assert np.__version__ == '1.20.2'
 ```
 
 ## Table of contents
 - [Installation](#installation)
+  - [Latest Stable PyPI Release](#latest-stable-pypi-release)
+  - [Latest GitHub Update](#latest-github-update)
 - [Overview](#overview)
   - [Smuggling Missing Packages](#smuggling-missing-packages)
   - [Smuggling Specific Package Versions](#smuggling-specific-package-versions)
   - [Use Cases](#use-cases)
-    - [Simplify Sharing Reproducible Code & Coding Environments](#simplify-sharing-reproducible-code--coding-environments)
+    - [Simplify Sharing Reproducible Code & Python Environments](#simplify-sharing-reproducible-code--python-environments)
     - [Guarantee your code always uses the latest version, release, or revision](#guarantee-your-code-always-uses-the-latest-version-release-or-revision)
     - [Compare behavior across package versions](#compare-behavior-across-package-versions)
 - [Usage](#usage)
@@ -53,23 +79,33 @@ assert np.__version__ == '1.20.2'
   - [The Onion Comment](#the-onion-comment)
     - [Syntax](#onion-comment-syntax)
     - [Rules](#onion-comment-rules)
-  - [Customizing `davos` Behavior](#customizing-davos-behavior)
-- [Examples](#examples)
-- [How it works](#how-it-works)
-- [FAQ](#faq)
-- [Limitations & Final Notes](#limitations--final-notes)
+  - [The `davos` Config](#the-davos-config)
+    - [Reference](#config-reference)
+    - [Top-level Functions](#top-level-functions)
+- [How It Works: The `davos` Parser](#how-it-works-the-davos-parser)
+- [Additional Notes](#additional-notes)
+  - [Reimplementing installer programs' CLI parsers](#notes-reimplement-cli)
+  - [Installer options that affect `davos` behavior](#notes-installer-opts)
+  - [Smuggling packages with C-extensions](#notes-c-extensions)
+  - [_`from` ... `import` ..._ statements and reloading modules](#notes-from-reload)
+  - [Smuggling packages from version control systems](#notes-vcs-smuggle)
 
 
 ## Installation
-### Latest stable PyPI release
-🟥🟥🟥 add badges 🟥🟥🟥
+### Latest Stable PyPI Release
+[![](https://img.shields.io/pypi/v/davos?label=PyPI&logo=pypi)](https://pypi.org/project/davos/)
+[![](https://img.shields.io/pypi/status/davos)]((https://pypi.org/project/davos/))
+[![](https://img.shields.io/pypi/format/davos)]((https://pypi.org/project/davos/))
 ```sh
 pip install davos
 ```
 
 
-### Latest unstable GitHub update
-🟥🟥🟥 add badges 🟥🟥🟥
+### Latest GitHub Update
+[![](https://img.shields.io/github/commits-since/ContextLab/davos/latest)](https://github.com/ContextLab/davos/releases)
+[![](https://img.shields.io/github/last-commit/ContextLab/davos?logo=git&logoColor=white)](https://github.com/ContextLab/davos/commits/main)
+[![](https://img.shields.io/github/release-date/ContextLab/davos?label=last%20release)](https://github.com/ContextLab/davos/releases/latest)
+
 ```sh
 pip install git+https://github.com/ContextLab/davos.git#egg=davos
 ```
@@ -77,113 +113,110 @@ pip install git+https://github.com/ContextLab/davos.git#egg=davos
 
 ### Installing in Colaboratory
 To use `davos` in [Google Colab](https://colab.research.google.com/), add a cell at the top of your notebook with an 
-exclamation point (`!`) followed by one of the install commands above (e.g., `!pip install davos`). Run the cell to 
-install `davos` on the runtime virtual machine. 
+exclamation point (`!`) followed by one of the commands above (e.g., `!pip install davos`). Run the cell to install 
+`davos` on the runtime virtual machine. 
 
-Note that restarting the notebook runtime does not affect installed packages. However, if the runtime is "factory reset" 
+**Note**: restarting the Colab runtime does not affect installed packages. However, if the runtime is "factory reset" 
 or disconnected due to reaching its idle timeout limit, you'll need to rerun the cell to reinstall `davos` on the fresh 
 VM instance.
 
 
 ## Overview
 The primary way to use `davos` is via [the `smuggle` statement](#the-smuggle-statement), which is made available 
-throughout your code simply by running `import davos`. Like 
+simply by running `import davos`. Like 
 [the built-in `import` statement](https://docs.python.org/3/reference/import.html), the `smuggle` statement is used to 
-load code from another package or module into the current namespace. The main difference between the two is in how they 
-handle missing packages and package versions.
+load packages, module, and other objects into the current namespace. The main difference between the two is in how 
+they handle missing packages and specific package versions.
 
 
 ### Smuggling Missing Packages
-`import` requires that packages be installed _before_ the start of the interpreter session. If you try to `import` a 
-package that can't be found locally, Python will raise a 
-[`ModuleNotFoundError`](https://docs.python.org/3/library/exceptions.html#ModuleNotFoundError), and you will have to 
-install the package, restart the interpreter to make the new package available, and rerun your code in full.
-
-`smuggle`, however, can handle missing packages on the fly. If you `smuggle` a package that isn't installed locally, 
-`davos` will install it, immediately make its contents accessible to the interpreter's
-[import machinery](https://docs.python.org/3/reference/import.html), and load the package into the local namespace for 
-use. You can add an inline ["onion" comment](#the-onion-comment) after a `smuggle` statement to customize how 
-`davos` will install the package, if it can't be found locally.
-
-An [onion comment](#the-onion-comment) is also useful for smuggling a package whose _distribution name_ (i.e., the name 
-used when installing it) is different from its _top-level module name_ (i.e., the name used when importing it). For 
-example:
-```python
-from sklearn.decomposition smuggle pca    # pip: scikit-learn
-```
-The [onion comment](#the-onion-comment) here (`# pip: scikit-learn`) tells `davos` that if "`sklearn`" does not exist 
-locally, the "`scikit-learn`" package should be installed.
+`import` requires that packages be installed _before_ the start of the interpreter session. Trying to `import` a package 
+that can't be found locally will throw a 
+[`ModuleNotFoundError`](https://docs.python.org/3/library/exceptions.html#ModuleNotFoundError), and you'll have to 
+install the package from the command line, restart the Python interpreter to make the new package importable, and rerun 
+your code in full in order to use it.
 
+**The `smuggle` statement, however, can handle missing packages on the fly**. If you `smuggle` a package that isn't 
+installed locally, `davos` will install it for you, make its contents available to Python's 
+[import machinery](https://docs.python.org/3/reference/import.html), and load it into the namespace for immediate use. 
+You can control _how_ `davos` installs missing packages by adding a special type of inline comment called an 
+["onion" comment](#the-onion-comment) next to a `smuggle` statement.
 
-### Smuggling Specific Package Versions
-[Onion comments](#the-onion-comment) can also be used to make `smuggle` statements version-sensitive. 
-
-Python does not provide a way to programmatically ensure a package imported at runtime matches a specific version, or 
-fits a particular [version constraint](https://www.python.org/dev/peps/pep-0440/#version-specifiers). Although many 
-packages expose their version info via a `__version__` attribute (see 
-[PEP 396](https://www.python.org/dev/peps/pep-0396/)), using this to enforce package versions is generally not viable, 
-as doing so would require:
-
-- writing a potentially large amount of additional code to compare package versions
-- replacing all `from foo import bar` statements with `import foo` to make `foo.__version__` accessible
-- manually installing the desired version, restarting the interpreter, and rerunning your code in full every time an 
-  invalid version is found
-- devising an entirely separate approach for packages that don't have a `__version__` attribute in the first place!
 
-Additionally, for packages installed through a version control system (e.g., git), this would be insensitive to 
-differences between revisions (e.g., commits) within the same semantic version.
-
-**`davos` solves these issues** by allowing you to constrain each package you `smuggle` to a specific version or range 
-of acceptable versions. This can be done simply by placing a 
+### Smuggling Specific Package Versions
+One simple but powerful use for [onion comments](#the-onion-comment) is making `smuggle` statements version-sensitive. 
+
+Python does not provide native, viable a way to ensure a third-party package imported at runtime matches a specific 
+version or fits a particular [version constraint](https://www.python.org/dev/peps/pep-0440/#version-specifiers). 
+Many packages expose their version info via a top-level `__version__` attribute (see 
+[PEP 396](https://www.python.org/dev/peps/pep-0396/)), and certain tools (such as the standard library's 
+[`importlib.metadata`](https://docs.python.org/3/library/importlib.metadata.html) and 
+[`setuptools`](https://setuptools.readthedocs.io/en/latest/index.html)'s 
+[`pkg_resources`](https://setuptools.readthedocs.io/en/latest/pkg_resources.html)) attempt to parse version info from 
+installed distributions. However, using these to constrain imported package would require writing extra code to compare 
+version strings and _still_ having manually installing the desired version and restarting the interpreter any time an 
+invalid version is caught.
+
+Additionally, for packages installed through a version control system (e.g., [git](https://git-scm.com/)), this would be 
+insensitive to differences between revisions (e.g., commits) within the same semantic version.
+
+**`davos` solves these issues** by allowing you to specify a specific version or set of acceptable versions for each 
+smuggled package. To do this, simply provide a 
 [version specifier](https://www.python.org/dev/peps/pep-0440/#version-specifiers) in an 
-[onion comment](#the-onion-comment) next to the corresponding `smuggle` statement:
+[onion comment](#the-onion-comment) next to the `smuggle` statement:
 ```python
 smuggle numpy as np              # pip: numpy==1.20.2
 from pandas smuggle DataFrame    # pip: pandas>=0.23,<1.0
 ```
 In this example, the first line will load [`numpy`](https://numpy.org/) into the local namespace under the alias "`np`", 
-just as "`import numpy as np`" would. `davos` will first check whether `numpy` is installed locally, and if so, whether 
+just as "`import numpy as np`" would. First, `davos` will check whether `numpy` is installed locally, and if so, whether 
 the installed version _exactly_ matches `1.20.2`. If `numpy` is not installed, or the installed version is anything 
-other than `1.20.2`, `davos` will use the specified _installer_, `pip`, to install `numpy==1.20.2` before loading the 
-package. 
+other than `1.20.2`, `davos` will use the specified _installer program_, [`pip`](https://pip.pypa.io/en/stable/), to 
+install `numpy==1.20.2` before loading the package. 
 
 Similarly, the second line will load the "`DataFrame`" object from the [`pandas`](https://pandas.pydata.org/) library, 
 analogously to "`from pandas import DataFrame`". A local `pandas` version of `0.24.1` would be used, but a local version 
-of `1.0.2` would cause `davos` to install a valid `pandas` version as if you had manually run `pip install 
+of `1.0.2` would cause `davos` to replace it with a valid `pandas` version, as if you had manually run `pip install 
 pandas>=0.23,<1.0`. 
 
 In both cases, the imported versions will fit the constraints specified in their [onion comments](#the-onion-comment), 
 and the next time `numpy` or `pandas` is smuggled with the same constraints, valid local installations will be found.
 
-You can also ensure the state of a packages matches a specific VCS branch, revision, ref, or release. For example:
+You can also force the state of a smuggled packages to match a specific VCS ref (branch, revision, tag, releas, etc.). 
+For example:
 ```python
-smuggle hypertools as hyp    # pip: git+https://github.com/ContextLab/hypertools.git@36c12fd
+smuggle hypertools as hyp    # pip: git+https://github.com/ContextLab/hypertools.git@98a3d80
 ```
 will load [`hypertools`](https://hypertools.readthedocs.io/en/latest/) (aliased as "`hyp`"), as the package existed 
 [on GitHub](https://github.com/ContextLab/hypertools), at commit 
-[36c12fd](https://github.com/ContextLab/hypertools/tree/36c12fd). The general format for VCS references in 
+[98a3d80](https://github.com/ContextLab/hypertools/tree/98a3d80). The general format for VCS references in 
 [onion comments](#the-onion-comment) follows that of the 
-[`pip-install`](https://pip.pypa.io/en/stable/cli/pip_install/#vcs-support) command. See the 
-[limitations section on smuggling from VCS](#limitation-vcs-smuggle) for additional notes.
+[`pip-install`](https://pip.pypa.io/en/stable/topics/vcs-support) command. See the 
+[notes on smuggling from VCS](#notes-vcs-smuggle) below for additional info.
 
-With [a few exceptions](#limitation-c-extensions), smuggling a specific package version will work _even if the package 
-has already been imported_.
+And with [a few exceptions](#notes-c-extensions), smuggling a specific package version will work _even if the package 
+has already been imported_!
+
+**Note**: `davos` v0.1 supports [IPython](https://ipython.readthedocs.io/en/stable/) environments  (e.g., 
+[Jupyter](https://jupyter.org/) and [Colaboratory](https://colab.research.google.com/) notebooks) only. v0.2 will add 
+support for "regular" (i.e., non-interactive) Python scripts.
 
 
 ### Use Cases
-#### Simplify sharing reproducible code & coding environments
+#### Simplify sharing reproducible code & Python environments
 Different versions of the same package can often behave quite differently&mdash;bugs are introduced and fixed, features 
 are implemented and removed, support for Python versions is added and dropped, etc. Because of this, Python code that is 
-meant to be _reproducible_ (e.g., tutorials, demos, data analyses) is commonly shared alongside a set of a set of fixed 
-versions for each package used. And since there is no Python-native way to specify package versions at runtime (see 
-[above](#smuggling-specific-package-versions)), this often takes the form of a pre-configured development environment 
-(e.g., a [Docker](https://www.docker.com/) container), which can be cumbersome, slow to set up, resource-intensive, and 
-confusing for newer users, as well as require shipping both additional specification files _and_ instructions along with 
-your code. Even then, a well-intentioned user may alter the environment in a way that affects your carefully curated set 
-of pinned package versions (such as installing additional packages that trigger dependency updates).
+meant to be _reproducible_ (e.g., tutorials, demos, data analyses) is commonly shared alongside a set of fixed versions 
+for each package used. And since there is no Python-native way to specify package versions at runtime (see 
+[above](#smuggling-specific-package-versions)), this typically takes the form of a pre-configured development 
+environment the end user must build themselves (e.g., a [Docker](https://www.docker.com/) container or 
+[conda](https://docs.conda.io/en/latest/) environment), which can be cumbersome, slow to set up, resource-intensive, and 
+confusing for newer users, as well as require shipping both additional specification files _and_ setup instructions 
+along with your code. And even then, a well-intentioned user may alter the environment in a way that affects your 
+carefully curated set of pinned packages (such as installing additional packages that trigger dependency updates).
    
 Instead, `davos` allows you to share code with one simple instruction: _just `pip install davos`!_ Replace your `import` 
-statements with `smuggle` statements, add package versions in onion comments, and let `davos` take care of the rest. 
+statements with `smuggle` statements, pin package versions in onion comments, and let `davos` take care of the rest. 
 Beyond its simplicity, this approach ensures your predetermined package versions are in place every time your code is 
 run.
 
@@ -201,27 +234,29 @@ smuggle mypkg    # pip: git+https://username/reponame.git
 
 #### Compare behavior across package versions
 The ability to `smuggle` a specific package version even after a different version has been imported makes `davos` a 
-useful tool for comparing behavior across multiple versions of the same package, all within the same runtime:
+useful tool for comparing behavior across multiple versions of the same package, within the same interpreter session:
 ```python
-data = list(range(10))
-
-smuggle mypkg                    # pip: mypkg==0.1
-result1 = mypkg.my_func(data)
+def test_my_func_unchanged():
+    """Regression test for `mypkg.my_func()`"""
+    data = list(range(10))
+    
+    smuggle mypkg                    # pip: mypkg==0.1
+    result1 = mypkg.my_func(data)
 
-smuggle mypkg                    # pip: mypkg==0.2
-result2 = mypkg.my_func(data)
+    smuggle mypkg                    # pip: mypkg==0.2
+    result2 = mypkg.my_func(data)
 
-smuggle mypkg                    # pip: git+https://github.com/MyOrg/mypkg.git
-result3 = mypkg.my_func(data)
+    smuggle mypkg                    # pip: git+https://github.com/MyOrg/mypkg.git
+    result3 = mypkg.my_func(data)
 
-print(result1 == result2 == result3)
+    assert result1 == result2 == result3
 ```
 
 
 ## Usage
 ### The `smuggle` Statement
 #### <a name="smuggle-statement-syntax"></a>Syntax
-The `smuggle` statement is designed to be used in place of 
+The `smuggle` statement is meant to be used in place of 
 [the built-in `import` statement](https://docs.python.org/3/reference/import.html) and shares
 [its full syntactic definition](https://docs.python.org/3/reference/simple_stmts.html#the-import-statement):
 ```ebnf
@@ -245,19 +280,19 @@ relative_module ::=  "."* module | "."+
 </sup>
 
 
-In simpler terms, any valid syntax for `import` is also a valid syntax for `smuggle` (`smuggle foo`, `from foo.bar 
-smuggle baz as qux`, etc.). See [below](#valid-syntaxes) for a full list of valid forms.
+In simpler terms, **any valid syntax for `import` is also valid for `smuggle`**.
 
 
 #### <a name="smuggle-statement-rules"></a>Rules
 - Like `import` statements, `smuggle` statements are whitespace-insensitive, unless a lack of whitespace between two 
   tokens would cause them to be interpreted as a different token:
   ```python
-  from os.path smuggle dirname, join as opj                            # valid
-  from   os    . path    smuggle  dirname     ,join       as    opj    # valid
-  from os.path smuggle dirname, join asopj                             # invalid
+  from os.path smuggle dirname, join as opj                       # valid
+  from   os   . path   smuggle  dirname    ,join      as   opj    # also valid
+  from os.path smuggle dirname, join asopj                        # invalid ("asopj" != "as opj")
   ```
-- Any context that would cause an `import` statement _not_ to be executed will do the same to a `smuggle` statement:
+- Any context that would cause an `import` statement _not_ to be executed will have the same effect on a `smuggle` 
+  statement:
   ```python
   # smuggle matplotlib.pyplot as plt           # not executed
   print('smuggle matplotlib.pyplot as plt')    # not executed
@@ -286,7 +321,8 @@ smuggle baz as qux`, etc.). See [below](#valid-syntaxes) for a full list of vali
      # etc...
      ```
 - In [IPython](https://ipython.readthedocs.io/en/stable/) environments (e.g., [Jupyter](https://jupyter.org/) & 
-  [Colaboratory](https://colab.research.google.com/)) `smuggle` statements always load names into the global namespace:
+  [Colaboratory](https://colab.research.google.com/) notebooks) `smuggle` statements always load names into the global 
+  namespace:
   ```python
   # example.ipynb
   import davos
@@ -304,29 +340,24 @@ smuggle baz as qux`, etc.). See [below](#valid-syntaxes) for a full list of vali
   type(datetime)                               # raises NameError
   
   smuggle_example()
-  type(datetime)                               # executed
-  ```
-  However, this does not affect plain Python scripts:
-  ```python
-  # example.py
-  import davos
-  
-  
-  def smuggle_example():
-      smuggle datetime
-  
-  
-  smuggle_example()
-  type(datetime)                               # raises NameError
+  type(datetime)                               # returns
   ```
 
 
 ### The Onion Comment
 An _onion comment_ is a special type of inline comment placed on a line containing a `smuggle` statement. Onion comments 
 can be used to control how `davos`:
-- determines whether the `smuggle`d package should be installed
-- installs the `smuggle`d package, if necessary
+1. determines whether the smuggled package should be installed
+2. installs the smuggled package, if necessary
 
+Onion comments are also useful when smuggling a package whose _distribution name_ (i.e., the name 
+used when installing it) is different from its _top-level module name_ (i.e., the name used when importing it). Take for 
+example:
+```python
+from sklearn.decomposition smuggle pca    # pip: scikit-learn
+```
+The onion comment here (`# pip: scikit-learn`) tells `davos` that if "`sklearn`" does not exist 
+locally, the "`scikit-learn`" package should be installed.
 
 #### <a name="onion-comment-syntax"></a>Syntax
 Onion comments follow a simple but specific syntax, inspired in part by the 
@@ -352,27 +383,24 @@ where `installer` is the program used to install the package; `install_opt` is a
 "`install`" command; and `version_spec` may be a 
 [version specifier](https://www.python.org/dev/peps/pep-0440/#version-specifiers) defined by 
 [PEP 440](https://www.python.org/dev/peps/pep-0440) followed by a 
-[version string](https://www.python.org/dev/peps/pep-0440/#public-version-identifiers), or an alternative syntax valid 
-for the given `installer` program. For example, `pip` uses specific syntax for 
+[version string](https://www.python.org/dev/peps/pep-0440/#public-version-identifiers), or an alternative syntaxe valid 
+for the given `installer` program. For example, [`pip`](https://pip.pypa.io/en/stable/) uses specific syntaxes for 
 [local](https://pip.pypa.io/en/stable/cli/pip_install/#local-project-installs), 
 [editable](https://pip.pypa.io/en/stable/cli/pip_install/#editable-installs), and 
-[VCS-based](https://pip.pypa.io/en/stable/cli/pip_install/#vcs-support) installation, while `conda` supports 
-[additional specifier characters](https://docs.conda.io/projects/conda/en/latest/user-guide/concepts/pkg-specs.html#examples) 
-and three-part 
-[package match specifications](https://docs.conda.io/projects/conda/en/latest/user-guide/concepts/pkg-specs.html#package-match-specifications).
+[VCS-based](https://pip.pypa.io/en/stable/topics/vcs-support) installation.  
 
-Less formally, an onion comment simply consists of two parts, separated by a colon: 
-1. the name of the installer program
-2. the arguments passed to the program's "install" command
+Less formally, **an onion comment simply consists of two parts, separated by a colon**: 
+1. the name of the installer program (e.g., [`pip`](https://pip.pypa.io/en/stable/))
+2. arguments passed to the program's "install" command
 
 Thus, you can essentially think of writing an onion comment as taking the full shell command you would run to install 
 the package, and replacing "_install_" with "_:_". For instance, the command:
 ```sh
-pip install -I --no-cache-dir numpy==1.20.2 -vvv
+pip install -I --no-cache-dir numpy==1.20.2 -vvv --timeout 30
 ```
 is easily translated into an onion comment as:
 ```python
-smuggle numpy    # pip: -I --no-cache-dir numpy==1.20.2 -vvv
+smuggle numpy    # pip: -I --no-cache-dir numpy==1.20.2 -vvv --timeout 30
 ```
 
 In practice, onion comments are identified as matches for the
@@ -380,16 +408,38 @@ In practice, onion comments are identified as matches for the
 ```regex
 #+ *(?:pip|conda) *: *[^#\n ].+?(?= +#| *\n| *$)
 ```
+<sup>
+  <i>
+    NB: support for installing <code>smuggle</code>d packages via 
+    <a href="https://docs.conda.io/en/latest/"><code>conda</code></a> will be added in v0.2. For v0.1, 
+    "<code>pip</code>" should be used exclusively.
+  </i>
+</sup>
+
+**Note**: support for installing smuggled packages via the [`conda`](https://docs.conda.io/en/latest/) package manager 
+will be added in v0.2. For v0.1, onion comments should always specify "`pip`" as the `installer` program.
 
 
 #### <a name="onion-comment-rules"></a>Rules
-- An onion comment may be followed by unrelated inline comments, as long as they are separated by at least one space:
+- An onion comment must be placed on the same line as a `smuggle` statement; otherwise, it is not parsed:
+  ```python
+  # assuming the dateutil package is not installed...
+  
+  # pip: python-dateutil                       # <-- has no effect
+  smuggle dateutil                             # raises InstallerError (no "dateutil" package exists)
+  
+  smuggle dateutil                             # raises InstallerError (no "dateutil" package exists)
+  # pip: python-dateutil                       # <-- has no effect
+  
+  smuggle dateutil    # pip: python-dateutil   # installs "python-dateutil" package, if necessary
+  ```
+- An onion comment may be followed by unrelated inline comments as long as they are separated by at least one space:
   ```python
   smuggle tqdm    # pip: tqdm>=4.46,<4.60 # this comment is ignored
   smuggle tqdm    # pip: tqdm>=4.46,<4.60            # so is this one
   smuggle tqdm    # pip: tqdm>=4.46,<4.60# but this comment raises OnionArgumentError
   ```
-- An onion comment must be the first inline comment immediately following a `smuggle` statement, otherwise it is not 
+- An onion comment must be the first inline comment immediately following a `smuggle` statement; otherwise, it is not 
   parsed:
   ```python
   smuggle numpy    # pip: numpy!=1.19.1        # <-- guarantees smuggled version is *not* v1.19.1
@@ -399,21 +449,11 @@ In practice, onion comments are identified as matches for the
   ```python
   smuggle numpy    ## pip: numpy!=1.19.1       # <-- has no effect
   ```
-- Onion comments must be placed on the same line as the `smuggle` statement, otherwise they are not parsed:
-  ```python
-  # pip: python-dateutil                       # <-- has no effect
-  smuggle dateutil                             # raises InstallerError (no "dateutil" package exists)
-  
-  smuggle dateutil                             # raises InstallerError (no "dateutil" package exists)
-  # pip: python-dateutil                       # <-- has no effect
-  
-  smuggle dateutil    # pip: python-dateutil   # installs "python-dateutil" package, if necessary
-  ```
 - Onion comments are generally whitespace-insensitive, but installer arguments must be separated by at least one space:
   ```python
-  from umap smuggle UMAP    # pip: umap-learn --user -v --no-clean       # valid
-  from umap smuggle UMAP#pip     :umap-learn    --user -v    --no-clean  # valid
-  from umap smuggle UMAP    # pip: umap-learn --user -v--no-clean        # raises OnionArgumentError
+  from umap smuggle UMAP    # pip: umap-learn --user -v --no-clean     # valid
+  from umap smuggle UMAP#pip:umap-learn --user     -v    --no-clean    # also valid
+  from umap smuggle UMAP    # pip: umap-learn --user-v--no-clean       # raises OnionArgumentError
   ```
 - Onion comments have no effect on standard library modules:
   ```python
@@ -424,12 +464,12 @@ In practice, onion comments are identified as matches for the
   ```python
   smuggle nilearn, nibabel, nltools    # pip: nilearn==0.7.1
   ```
-- If multiple _separate_ `smuggle` statements appear on a single line separated by semicolons, an onion comment 
-  may be used to modify the **last** `smuggle` statement:
+- If multiple _separate_ `smuggle` statements are placed on a single line, an onion comment may be used to refer to the 
+  **last** statement:
   ```python
   smuggle gensim; smuggle spacy; smuggle nltk    # pip: nltk~=3.5 --pre
   ```
-- For multiline `smuggle` statements, an onion comment may be placed on either the first or last line:
+- For multiline `smuggle` statements, an onion comment may be placed on the first line:
   ```python
   from scipy.interpolate smuggle (    # pip: scipy==1.6.3
       interp1d,
@@ -437,99 +477,218 @@ In practice, onion comments are identified as matches for the
       LinearNDInterpolator,
       NearestNDInterpolator,
   )
-  
-  from scipy.interpolate smuggle (interp1d,    # this comment and has no effect
+  ```
+  ... or on the last line:
+  ```python
+  from scipy.interpolate smuggle (interp1d,                  # this comment has no effect
                                   interpn as interp_ndgrid,
                                   LinearNDInterpolator,
-                                  NearestNDInterpolator)    # pip: scipy==1.6.3
+                                  NearestNDInterpolator)     # pip: scipy==1.6.3
   ```
-  though the first line takes priority:
+  ... though the first line takes priority:
   ```python
   from scipy.interpolate smuggle (    # pip: scipy==1.6.3    # <-- this version is installed
       interp1d,
       interpn as interp_ndgrid,
       LinearNDInterpolator,
       NearestNDInterpolator,
-  )    # pip: scipy==1.6.2    # <-- this comment is ignored
+  )    # pip: scipy==1.6.2                                   # <-- this comment is ignored
   ```
-  and all comments _not_ on the first or last line are ignored:
+  ... and all comments _not_ on the first or last line are ignored:
   ```python
-  from scipy.interpolate smuggle (interp1d,
-                                  interpn as interp_ndgrid,    # pip: scipy==1.6.3    # <-- ignored
-                                  LinearNDInterpolator,    # unrelated comment        # <-- ignored
-                                  NearestNDInterpolator)    # pip: scipy==1.6.2       # <-- parsed
+  from scipy.interpolate smuggle (
+      interp1d,                       # pip: scipy==1.6.3    # <-- ignored
+      interpn as interp_ndgrid,
+      LinearNDInterpolator,           # unrelated comment    # <-- ignored
+      NearestNDInterpolator
+  )                                   # pip: scipy==1.6.2    # <-- parsed
+  ```
+- The onion comment is intended to describe how a specific smuggled package should be installed if it is not found 
+  locally, in order to make it available for immediate use. Therefore, installer options that either (A) install 
+  packages other than the smuggled package and its dependencies (e.g., from a specification file), or (B) cause the 
+  smuggled package not to be installed, are disallowed. The options listed below will raise an `OnionArgumentError`:
+  - `-h`, `--help`
+  - `-r`, `--requirement`
+  - `-V`, `--version`
+
+
+### The `davos` Config
+The `davos` config object stores options and data that affect how `davos` behaves. After importing `davos`, the config 
+instance (a singleton) for the current session is available as `davos.config`, and its various fields are accessible as 
+attributes. The config object exposes a mixture of writable and read-only fields. Most `davos.config` attributes can be 
+assigned values to control aspects of `davos` behavior, while others are available for inspection but are set and used 
+internally. Additionally, certain config fields may be writable in some situations but not others (e.g. only if the 
+importing environment supports a particular feature). Once set, `davos` config options last for the lifetime of the 
+interpreter (unless updated); however, they do *not* persist across interpreter sessions. A full list of `davos` config 
+fields is available [below](#config-reference):
+
+#### <a name="config-reference"></a>Reference
+| Field | Description | Type | Default | Writable? |
+| :---: | --- | :---: | :---: | :---: |
+| `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` | ✅ |
+| `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**) |
+| `confirm_install` | Whether or not `davos` should require user confirmation (`[y/n]` input) before installing a smuggled package | `bool` | `False` | ✅ |
+| `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<7.0', 'IPython>=7.0', 'Colaboratory']` | N/A | ❌ |
+| `ipython_shell` | The global IPython interactive shell instance | [`IPython.core`<br>`.interactiveshell`<br>`.InteractiveShell`](https://ipython.readthedocs.io/en/stable/api/generated/IPython.core.interactiveshell.html#IPython.core.interactiveshell.InteractiveShell) | N/A | ❌ |
+| `noninteractive` | Set to `True` to run `davos` in non-interactive mode (all user input and confirmation will be disabled). **NB**:<br>1. Setting to `True` disables `confirm_install` if previously enabled <br>2. 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**) |
+| `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` | ✅ |
+| `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]` | `{}` | ❌ |
+| `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 & stderr will be shown with the traceback | `bool` | `False` | ✅ |
+
+#### <a name="top-level-functions"></a>Top-level Functions
+`davos` also provides a few convenience for reading/setting config values:
+- **`davos.activate()`**
+  Activate the `davos` parser, enable the `smuggle` keyword, and inject the `smuggle()` function into the namespace. 
+  Equivalent to setting `davos.config.active = True`. See [How it Works](#how-it-works) for more info.
+
+- **`davos.deactivate()`**
+  Deactivate the `davos` parser, disable the `smuggle` keyword, and remove the name `smuggle` from the namespace if (and 
+  only if) it refers to the `smuggle()` function. If `smuggle` has been overwritten with a different value, the variable 
+  will not be deleted. Equivalent to setting `davos.config.active = False`. See [How it Works](#how-it-works) for more 
+- info.
+
+- **`davos.is_active()`**
+  Return the current value of `davos.config.active`.
+
+- **`davos.configure(**kwargs)`**
+  Set multiple `davos.config` fields at once by passing values as keyword arguments, e.g.:
+  ```python
+  import davos
+  davos.configure(active=False, noninteractive=True, pip_executable='/usr/bin/pip3')
+  ```
+  is equivalent to:
+  ```python
+  import davos
+  davos.active = False
+  davos.noninteractive = True
+  davos.pip_executable = '/usr/bin/pip3'
   ```
-- Because the onion comment is meant to control installation of a single `smuggle`d package, and because the purpose of 
-  installing that package is to make it available for immediate use, installer options that either A) install more than 
-  a single package and its dependencies (e.g., from a specification file), or B) do not install the specified package 
-  are disallowed. The options listed below for each installer will raise an `OnionArgumentError`:
-  - pip:
-    - `-r`/`--requirement`
-  - conda:
-    - `-d`/`--dry-run`
-    - `--download-only`
-    - `--file`
-    - `--only-deps`
-    - `--update-all`/`--all`
-
-
-### Customizing `davos` Behavior
-## Examples
-- smuggle specific version
-- smuggle package from VCS
-- smuggle package from local dir
-- smuggle editable package
-- smuggle package with extra requirements
-- smuggle latest version fo package
-### Valid Syntaxes
-## How it works
-### Google Colab
-### Jupyter Notebooks
-### Python scripts
-## FAQ
-
-
-## Limitations & Final Notes
-- <a name="limitation-c-extensions"></a>**Smuggling packages with C-extensions**
+
+## How It Works: The `davos` Parser
+Functionally, importing `davos` appears to enable a new Python keyword, "_`smuggle`_". However, `davos` doesn't actually 
+modify the rules or [reserved keywords](https://docs.python.org/3/reference/lexical_analysis.html#keywords) used by 
+Python's parser and lexical analyzer in order to do so&mdash;in fact, modifying the Python grammar is not possible at 
+runtime and would require rebuilding the interpreter. Instead, in [IPython](https://ipython.readthedocs.io/en/stable/) 
+enivonments like [Jupyter](https://jupyter.org/) and 
+[Colaboratory](https://colab.research.google.com/notebooks/intro.ipynb) notebooks, `davos` implements the `smuggle` 
+keyword via a combination of namespace injections and its own (far simpler) custom parser.
+
+The `smuggle` keyword can be enabled and disabled at will by "activating" and "deactivating" `davos` (see the 
+[`davos` Config Reference](config-reference) and [Top-level Functions](#top-level-functions), above). When `davos` is 
+imported, it is automatically activated by default. Activating `davos` triggers two things:
+1. The _`smuggle()` function_ is injected into the `IPython` user namespace
+2. The _`davos` parser_ is registered as a
+[custom input transformer](https://ipython.readthedocs.io/en/stable/config/inputtransforms.html)
+
+IPython preprocesses all executed code as plain text before it is sent to the Python parser in order to handle 
+special constructs like [`%magic`](https://ipython.readthedocs.io/en/stable/interactive/magics.html) and 
+[`!shell`](https://ipython.readthedocs.io/en/stable/interactive/reference.html#system-shell-access) commands. `davos`
+hooks into this process to transform `smuggle` statements into syntactically valid Python code. The `davos` 
+parser uses [this regular expression](https://github.com/ContextLab/davos/blob/main/davos/core/regexps.py) to match each
+line of code containing a `smuggle` statement (and, optionally, an onion comment), extracts information from its text, 
+and replaces it with an analogous call to the _`smuggle()` function_. Thus, even though the code visible to the user may 
+contain `smuggle` statements, e.g.:
+```python
+smuggle numpy as np    # pip: numpy>1.16,<=1.20 -vv
+```
+the code that is actually executed by the Python interpreter will not:
+```python
+smuggle(name="numpy", as_="np", installer="pip", args_str="""numpy>1.16,<=1.20 -vv""", installer_kwargs={'editable': False, 'spec': 'numpy>1.16,<=1.20', 'verbosity': 2})
+```
+
+The `davos` parser can be deactivated at any time, and doing so triggers the opposite actions of activating it:
+1. The name "`smuggle`" is deleted from the `IPython` user namespace, *unless it has been overwritten and no longer 
+   refers to the `smuggle()` function*
+2. The `davos` parser input transformer is deregistered.
+
+**Note**: in Jupyter and Colaboratory notebooks, IPython parses and transforms all text in a cell before sending it 
+to the kernel for execution. This means that importing or activating `davos` will not make the `smuggle` statement 
+available until the _next_ cell, because all lines in the current cell were transformed before the `davos` parser was 
+registered. However, _deactivating_ `davos` disables the `smuggle` statement immediately&mdash;although the `davos` 
+parser will have already replaced all `smuggle` statements with `smuggle()` function calls, removing the function from 
+the namespace causes them to throw `NameError`.
+
+
+## Additional Notes
+- <a name="notes-reimplement-cli"></a>**Reimplementing installer programs' CLI parsers**
+
+  The `davos` parser extracts info from onion comments by passing them to a (slightly modified) reimplementation of 
+  their specified installer program's CLI parser. This is somewhat redundant, since the arguments will eventually be 
+  re-parsed by the _actual_ installer program if the package needs to be installed. However, it affords a number of 
+  advantages, such as: 
+  - detecting errors early during the parser phase, before spending any time running code above the line containing the 
+    `smuggle` statement
+  - preventing shell injections in onion comments&mdash;e.g., `#pip: --upgrade numpy && rm -rf /` fails due to the 
+    `OnionParser`, but would otherwise execute successfully.
+  - allowing certain installer arguments to temporarily influence `davos` behavior while smuggling the current package 
+    (see [Installer options that affect `davos` behavior](#notes-installer-opts) below for specific info)
+
+- <a name="notes-installer-opts"></a>**Installer options that affect `davos` behavior**
   
+  Passing certain options to the installer program via an [onion comment](#the-onion-comment) will also affect the 
+  corresponding `smuggle` statement in a predictable way:
+
+  - [**`--force-reinstall`**](https://pip.pypa.io/en/stable/cli/pip_install/#cmdoption-force-reinstall) | 
+    [**`-I`, `--ignore-installed`**](https://pip.pypa.io/en/stable/cli/pip_install/#cmdoption-I) | 
+    [**`-U`, `--upgrade`**](https://pip.pypa.io/en/stable/cli/pip_install/#cmdoption-I)
+      
+    The package will be installed, even if it exists locally
+
+  - [**`--no-input`**](https://pip.pypa.io/en/stable/cli/pip/#cmdoption-no-input)
+      
+    Disables input prompts, analogous to temporarily setting `davos.config.noninteractive` to `True`. Overrides value 
+    of `davos.config.confirm_install`.
+
+  - [**`--src <dir>`**](https://pip.pypa.io/en/stable/cli/pip/#cmdoption-no-input) | 
+    [**`-t`, `--target <dir>`**](https://pip.pypa.io/en/stable/cli/pip_install/#cmdoption-t)
+      
+    Prepends `<dir>` to [`sys.path`](https://docs.python.org/3/library/sys.html#sys.path) if not already present so 
+    the package can be imported.
+    
+- <a name="notes-c-extensions"></a>**Smuggling packages with C-extensions**
+
   Some Python packages that rely heavily on custom data types implemented via 
   [C-extensions](https://docs.python.org/3.9/extending/extending.html) (e.g., `numpy`, `pandas`) dynamically generate 
   modules defining various C functions and data structures, and link them to the Python interpreter when they are first 
   imported. Depending on how these objects are initialized, they may not be subject to normal garbage collection, and 
   persist despite their reference count dropping to zero. This can lead to unexpected errors when reloading the Python 
   module that creates them, particularly if their dynamically generated source code has been changed (e.g., because the 
-  reloaded package is a different version).
+  reloaded package is a newer version).
   
   This can occasionally affect `davos`'s ability to `smuggle` a new version of a package (or dependency) that was 
-  previously `import`ed. To handle this, `davos` first checks each package it installs against 
+  previously imported. To handle this, `davos` first checks each package it installs against 
   [`sys.modules`](https://docs.python.org/3.9/library/sys.html#sys.modules). If a different version has already been 
-  loaded by the interpreter, `davos` will attempt to replace it with the requested version (in the vast majority of 
-  cases, this is not a problem). However, if this fails due to a C-extension-related issue, `davos` will reinstate the 
-  old package version _in memory_, while replacing it with the new package version _on disk_.    
-  🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥
-  **describe behavior after implementing option to trigger restart & rerun cells above**
+  loaded by the interpreter, `davos` will attempt to replace it with the requested version. If this fails, `davos` will 
+  restore the old package version _in memory_, while replacing it with the new package version _on disk_. This allows 
+  subsequent code that uses the non-reloadable module to still execute in most cases, while dependency checks for other 
+  packages run against the updated version. Then, depending on the value of `davos.config.auto_rerun`, `davos` will 
+  either either automatically restart the interpreter to load the updated package, prompt you to do so, or raise an 
+  exception.
+
+- <a name="notes-from-reload"></a>**_`from` ... `import` ..._ statements and reloading modules**
+
+  The Python docs for [`importlib.reload()`](https://docs.python.org/3/library/importlib.html#importlib.reload) include 
+  the following caveat:
+  > If a module imports objects from another module using 
+  > [`from`](https://docs.python.org/3/reference/simple_stmts.html#from) … 
+  > [`import`](https://docs.python.org/3/reference/simple_stmts.html#import) …, calling 
+  > [`reload()`](https://docs.python.org/3/library/importlib.html#importlib.reload) for the other module does 
+  > not redefine the objects imported from it — one way around this is to re-execute the `from` statement, another is to 
+  > use `import` and qualified names (_module.name_) instead.
+
+  The same applies to smuggling packages or modules from which objects have already been loaded. If object _`name`_ from 
+  module _`module`_ was loaded using either _`from module import name`_ or _`from module smuggle name`_, subsequently 
+  running _`smuggle module    # pip --upgrade`_ will in fact install and load an upgraded version of _`module`_, but the 
+  the _`name`_ object will still be that of the old version! To fix this, you can simply run _`from module smuggle 
+  name`_ either instead in lieu of or after _`smuggle module`_.
   
-[comment]: <> (  In a Jupyter/Colbab )
-
-[comment]: <> (  notebook, `davos` will prompt you to restart the kernel while allowing the remaining code in the current cell to )
-
-[comment]: <> (  execute. )
-  
-[comment]: <> (  This way:)
-
-[comment]: <> (    - the `smuggle` statement finishes executing without error)
-
-[comment]: <> (    - )
 
-[comment]: <> (    - the next time the interpreter is launched, the `smuggle`d version will be used)
+- <a name="notes-vcs-smuggle"></a>**Smuggling packages from version control systems**
 
-- <a name="limitation-vcs-smuggle"></a>**Smuggling packages from version control systems**
-  - To `smuggle` a package from a local or remote VCS URL, you must specify `pip` (i.e., not `conda`) as the  
-    [installer](#smuggle-statement-syntax), as only `pip` supports VCS installation.
-  - The first time during an interpreter session that a given package is installed from a VCS URL, it is assumed not to 
-    be present locally, and is therefore freshly installed. `pip` clones non-editable VCS repositories into a temporary 
-    directory, installs them with setuptools, and then immediately deletes them. Since no information is retained about 
-    the state of the repository at installation, it is impossible to determine whether an existing package satisfies the 
-    state (branch, commit hash, etc.) requested for `smuggle`d package.
+  The first time during an interpreter session that a given package is installed from a VCS URL, it is assumed not to be 
+  present locally, and is therefore freshly installed. `pip` clones non-editable VCS repositories into a temporary 
+  directory, runs `setup.py install`, and then immediately deletes them. Since no information is retained about the 
+  state of the repository at installation, it is impossible to determine whether an existing package satisfies the state 
+  (i.e., branch, tag, commit hash, etc.) requested for smuggled package.
 
 [comment]: <> (- 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. )
diff --git a/davos/__init__.py b/davos/__init__.py
index 371a825f..558a6205 100644
--- a/davos/__init__.py
+++ b/davos/__init__.py
@@ -1,3 +1,10 @@
+"""
+Top-level `davos` module. Initializes the global `davos` config object
+and defines some convenience functions for accessing/setting
+`davos.config` values.
+"""
+
+
 import pkg_resources
 from davos.core.config import DavosConfig
 
@@ -33,6 +40,7 @@ def activate():
     config.active = True
 
 
+# pylint: disable=unused-argument
 def configure(
         *,
         active=...,
@@ -88,7 +96,7 @@ def configure(
             old_value = getattr(config, name)
             try:
                 setattr(config, name, new_value)
-            except:
+            except Exception:
                 # if one assignment fails, no config fields are updated
                 for _name, old_value in old_values.items():
                     setattr(config, _name, old_value)
diff --git a/davos/__init__.pyi b/davos/__init__.pyi
index d5f4b718..87497ff9 100644
--- a/davos/__init__.pyi
+++ b/davos/__init__.pyi
@@ -3,11 +3,11 @@ from davos.core.config import DavosConfig
 
 __all__ = list[
     Literal[
-        'activate', 
-        'config', 
-        'configure', 
-        'deactivate', 
-        'is_active', 
+        'activate',
+        'config',
+        'configure',
+        'deactivate',
+        'is_active',
         'smuggle'
     ]
 ]
@@ -17,12 +17,12 @@ config: DavosConfig
 
 def activate() -> None: ...
 def configure(
-        *, 
-        active: bool = ..., 
+        *,
+        active: bool = ...,
         auto_rerun: bool = ...,
-        conda_env: str = ..., 
-        confirm_install: bool = ..., 
-        noninteractive: bool = ..., 
+        conda_env: str = ...,
+        confirm_install: bool = ...,
+        noninteractive: bool = ...,
         suppress_stdout: bool = ...
 ) -> None: ...
 def deactivate() -> None: ...
diff --git a/davos/core/config.py b/davos/core/config.py
index 2ccee937..349fd351 100644
--- a/davos/core/config.py
+++ b/davos/core/config.py
@@ -11,9 +11,11 @@
 
 
 import pathlib
+import pprint
 import sys
 import traceback
 import warnings
+from io import StringIO
 from os.path import expandvars
 from subprocess import CalledProcessError, check_output
 
@@ -36,6 +38,7 @@ class DavosConfig(metaclass=SingletonConfig):
     The global `davos` config object.
 
     Defines the following fields:
+
         **Configurable fields**:
             active : bool
                 Whether the `davos` parser should be run on subsequent
@@ -43,19 +46,19 @@ class DavosConfig(metaclass=SingletonConfig):
                 Python; default: `True`)
             auto_rerun : bool
                 If `True` (default: `False`), automatically restart the
-                interpreter sesssion and rerun previously executed code
+                interpreter session and rerun previously executed code
                 upon smuggling a package that cannot be dynamically
                 reloaded (Note: currently implemented for Jupyter
                 notebooks only)
             conda_env: str or None
-                The name of the resident conda environemnt of the
+                The name of the resident conda environment of the
                 current Python interpreter, if running within a `conda`
                 environment. Otherwise, `None`.
             confirm_install : bool
                 If `True` (default: `False`), prompt for user input
                 before installing any smuggled packages not already
                 available locally.
-            nonineractive : bool
+            noninteractive : bool
                 If `True` (default: `False`) run `davos` in
                 non-interactive mode. All user input and confirmation
                 will be disabled. **Note**: In Jupyter environments, the
@@ -79,6 +82,14 @@ class DavosConfig(metaclass=SingletonConfig):
                 If `conda_avail` is `True`, a mapping of conda
                 environment names to their environment directories.
                 Otherwise, `False`.
+            environment : {'Python', 'IPython<7.0', 'IPython>=7.0',
+                          'Colaboratory'}
+                The environment in which `davos` is running. Determines
+                which interchangeable implementation functions are used,
+                whether certain config fields are writable, and various
+                other behaviors.
+            ipython_shell : IPython.core.interactiveshell.InteractiveShell
+                The global `IPython` interactive shell instance.
             smuggled : dict
                 A cache of packages previously smuggled during the
                 current interpreter session, implemented as a dict whose
@@ -89,6 +100,41 @@ class DavosConfig(metaclass=SingletonConfig):
                 re-installation.
     """
 
+    # noinspection PyUnusedLocal
+    # pylint: disable=unused-argument, missing-function-docstring, no-self-use
+    @staticmethod
+    def __mock_sorted(__iterable, key=None, reverse=False):
+        """
+        Mock for built-in `sorted` that returns unsorted iterable.
+
+        Used to temporarily monkeypatch built-in `sorted` function in
+        `pprint` module when formatting display for `self.__repr__`
+        method. `dict` items in `smuggled` field should be shown in
+        insertion order, not alphabetically, because they represent a
+        history of cached `smuggle` commands. However,
+        `pprint.PrettyPrinter` didn't support option to *not* sort
+        `dict`s until Python 3.8, so for earlier versions, this is
+        assigned to `pprint.sorted` so that it takes priority over the
+        built-in `sorted` in module's namespace lookup chain.
+
+        Parameters
+        ----------
+        __iterable : iterable
+            The iterable to be "sorted."
+        key : callable, optional
+            Has no effect; exists to make function signature match that
+            of built-in `sorted` function.
+        reverse : bool
+            Has no effect; exists to make function signature match that
+            of built-in `sorted` function.
+
+        Returns
+        -------
+        iterable
+            The object passed as `__iterable`, unaltered.
+        """
+        return __iterable
+
     # noinspection PyFinal
     # (PyCharm doesn't differentiate between declarations here and in
     # stub file, which it should, according to PEP 591)
@@ -97,7 +143,6 @@ def __init__(self):
         #           READ-ONLY FIELDS           #
         ########################################
         try:
-            # noinspection PyUnresolvedReferences
             # (function exists globally when imported into IPython context)
             self._ipython_shell = get_ipython()
         except NameError:
@@ -118,6 +163,10 @@ def __init__(self):
         self._conda_avail = None
         self._conda_envs_dirs = None
         self._ipy_showsyntaxerror_orig = None
+        self._repr_formatter = pprint.PrettyPrinter()
+        if sys.version_info.minor >= 8:
+            # sort_dicts constructor param added in Python 3.8
+            self._repr_formatter._sort_dicts = False
         self._smuggled = {}
         self._stdlib_modules = _get_stdlib_modules()
         ########################################
@@ -149,7 +198,7 @@ def __init__(self):
             except CalledProcessError:
                 # try one more thing before we just throw up our hands...
                 try:
-                    chceck_output([sys.executable, '-c', 'import pip'])
+                    check_output([sys.executable, '-c', 'import pip'])
                 except CalledProcessError as e:
                     raise DavosError(
                         "Could not find 'pip' executable in $PATH or package "
@@ -168,9 +217,55 @@ def __init__(self):
                 self._pip_executable = pip_exe
 
     def __repr__(self):
-        # TODO: implement me
-        #  also implement _repr_html_ and/or _repr_pretty_ et al.
-        return super().__repr__()
+        cls_name = self.__class__.__name__
+        base_indent = len(cls_name) + 1
+        attrs_in_repr = ['active', 'auto_rerun', 'conda_avail']
+        if self._conda_avail is not None:
+            attrs_in_repr.append('conda_avail')
+            if self._conda_avail is True:
+                attrs_in_repr.extend(['conda_env', 'conda_envs_dirs'])
+        attrs_in_repr.extend([
+            'confirm_install',
+            'environment',
+            'ipython_shell',
+            'noninteractive',
+            'pip_executable',
+            'suppress_stdout',
+            'smuggled'
+        ])
+        newline_delim = ',\n' + ' ' * base_indent
+        last_item_ix = len(attrs_in_repr) - 1
+        stream = StringIO()
+        stream.write(f'{cls_name}(')
+        try:
+            if sys.version_info.minor < 8:
+                # monkeypatch built-in sorted function in pprint module
+                # while formatting __repr__ output
+                pprint.sorted = self.__mock_sorted
+            for i, attr_name in enumerate(attrs_in_repr):
+                attr_indent = base_indent + len(attr_name) + 1
+                is_last = i == last_item_ix
+                stream.write(f'{attr_name}=')
+                self._repr_formatter._format(getattr(self, f'_{attr_name}'),
+                                             stream=stream,
+                                             indent=attr_indent,
+                                             allowance=int(not is_last),
+                                             context={},
+                                             level=0)
+                if not is_last:
+                    stream.write(newline_delim)
+        finally:
+            if sys.version_info.minor < 8:
+                try:
+                    # delete the patch function so it doesn't interfere
+                    # with other libraries using pprint module
+                    # noinspection PyUnresolvedReferences
+                    del pprint.sorted
+                except AttributeError:
+                    pass
+        repr_ = stream.getvalue()
+        stream.close()
+        return repr_ + ')'
 
     @property
     def auto_rerun(self):
@@ -181,7 +276,7 @@ def auto_rerun(self, value):
         if not isinstance(value, bool):
             raise DavosConfigError('auto_rerun',
                                    "field may be 'True' or 'False'")
-        elif self._environment == 'Colaboratory':
+        if self._environment == 'Colaboratory':
             raise DavosConfigError(
                 'auto_rerun',
                 'automatic rerunning of cells not available in Colaboratory'
@@ -197,7 +292,7 @@ def confirm_install(self, value):
         if not isinstance(value, bool):
             raise DavosConfigError('confirm_install',
                                    "field may be 'True' or 'False'")
-        elif self._noninteractive and value:
+        if self._noninteractive and value:
             raise DavosConfigError('confirm_install',
                                    "field may not be 'True' in noninteractive "
                                    "mode")
@@ -228,12 +323,12 @@ def noninteractive(self, value):
         if not isinstance(value, bool):
             raise DavosConfigError('noninteractive',
                                    "field may be 'True' or 'False'")
-        elif self._environment == 'Colaboratory':
+        if self._environment == 'Colaboratory':
             raise DavosConfigError(
                 'noninteractive',
                 "noninteractive mode not available in Colaboratory"
             )
-        elif value and self._confirm_install:
+        if value and self._confirm_install:
             warnings.warn(
                 "noninteractive mode enabled, setting `confirm_install = False`"
             )
diff --git a/davos/core/config.pyi b/davos/core/config.pyi
index fba5634c..1bd3cda6 100644
--- a/davos/core/config.pyi
+++ b/davos/core/config.pyi
@@ -1,12 +1,15 @@
+from collections.abc import Callable, Iterable
 from pathlib import PurePosixPath
+from pprint import PrettyPrinter
 from typing import (
-    Any, 
-    ClassVar, 
-    Final, 
-    Literal, 
-    NoReturn, 
-    Optional, 
-    Protocol, 
+    Any,
+    ClassVar,
+    Final,
+    Literal,
+    NoReturn,
+    Optional,
+    Protocol,
+    TypeVar,
     Union
 )
 from google.colab._shell import Shell                            # type: ignore
@@ -15,18 +18,19 @@ from IPython.core.interactiveshell import InteractiveShell       # type: ignore
 __all__: list[Literal['DavosConfig']]
 
 _Environment = Literal['Colaboratory', 'IPython<7.0', 'IPython>=7.0', 'Python']
+_I= TypeVar('_I', bound=Iterable)
 IpythonShell = Union[InteractiveShell, Shell]
 
 class IpyShowSyntaxErrorPre7(Protocol):
     def __call__(self, filename: Optional[str] = ...) -> None: ...
-    
+
 class IpyShowSyntaxErrorPost7(Protocol):
     def __call__(self, filename: Optional[str] = ..., running_compile_code: bool = ...) -> None: ...
 
 class SingletonConfig(type):
     __instance: ClassVar[Optional[DavosConfig]]
     def __call__(cls, *args: Any, **kwargs: Any) -> DavosConfig: ...
-    
+
 class DavosConfig(metaclass=SingletonConfig):
     _active: bool
     _auto_rerun: bool
@@ -39,9 +43,12 @@ class DavosConfig(metaclass=SingletonConfig):
     _ipython_shell: Final[Optional[IpythonShell]]
     _noninteractive: bool
     _pip_executable: str
+    _repr_formatter: PrettyPrinter
     _smuggled: dict[str, str]
     _stdlib_modules: Final[set[str]]
     _suppress_stdout: bool
+    @staticmethod
+    def __mock_sorted(__iterable: _I, key: Optional[Callable] = None, reverse: bool = False) -> _I: ...
     def __init__(self) -> None: ...
     def __repr__(self) -> str: ...
     @property
diff --git a/davos/core/core.py b/davos/core/core.py
index 9e1ce3b0..58933d4d 100644
--- a/davos/core/core.py
+++ b/davos/core/core.py
@@ -48,7 +48,7 @@
 )
 
 
-class capture_stdout:
+class capture_stdout:    # pylint: disable=invalid-name
     """
     Context manager for sending stdout to multiple streams at once.
 
@@ -87,12 +87,12 @@ def __enter__(self):
     def __exit__(self, exc_type, exc_value, exc_tb):
         sys.stdout.write = self.sys_stdout_write
         if self.closing:
-            for s in self.streams:
-                s.close()
+            for stream in self.streams:
+                stream.close()
 
     def _write(self, data):
-        for s in self.streams:
-            s.write(data)
+        for stream in self.streams:
+            stream.write(data)
         self.sys_stdout_write(data)
         sys.stdout.flush()
 
@@ -115,14 +115,6 @@ def check_conda():
         If the environment mapping could be parsed from the output of
         `conda info --envs`, but the active environment name couldn't be
         parsed from the output of `conda list IPython`
-
-    Notes
-    -----
-    The full shell command run to format the output of
-    `conda info --envs` is:
-    ```sh
-    conda info --envs | grep -E '^\w' | sed -E 's/ +\*? +/ /g'
-    ```
     """
     conda_list_output = _check_conda_avail_helper()
     if conda_list_output is None:
@@ -141,7 +133,7 @@ def check_conda():
                                               live_stdout=False)
         # noinspection PyTypeChecker
         envs_dirs_dict = dict(map(str.split, conda_info_output.splitlines()))
-    except Exception:
+    except Exception:    # pylint: disable=broad-except
         # if no environments are found or output can't be parsed for
         # some reason, just count any conda env provided as valid. This
         # doesn't cause any major problems, we just can't catch errors
@@ -155,9 +147,9 @@ def check_conda():
     # format of first line of output seems to reliably be: `# packages
     # in environment at /path/to/environment/dir:` but can't hurt to
     # parse more conservatively since output is so short
-    for w in conda_list_output.split():
-        if '/' in w:
-            env_name = w.split('/')[-1].rstrip(':')
+    for word in conda_list_output.split():
+        if '/' in word:
+            env_name = word.split('/')[-1].rstrip(':')
             if (
                     envs_dirs_dict is not None and
                     env_name not in envs_dirs_dict.keys()
@@ -223,8 +215,7 @@ def get_previously_imported_pkgs(install_cmd_stdout, installer):
         raise NotImplementedError(
             "conda-install stdout parsing is not yet implemented"
         )
-    else:
-        installed_pkg_regex = pip_installed_pkgs_regex
+    installed_pkg_regex = pip_installed_pkgs_regex
 
     matches = installed_pkg_regex.findall(install_cmd_stdout)
     if len(matches) == 0:
@@ -298,8 +289,7 @@ def import_name(name):
                 f'No module or object "{obj_name}" found in "{mod_name}"'
             ) from e
         return obj
-    else:
-        return __import__(parts[0])
+    return __import__(parts[0])
 
 
 class Onion:
@@ -461,6 +451,7 @@ def __init__(self, package_name, installer, args_str, **installer_kwargs):
 
     @property
     def install_cmd(self):
+        """The shell command run to install the package as specified"""
         if self.args_str == '':
             args = self.install_name
         else:
@@ -474,24 +465,25 @@ def install_cmd(self):
 
     @property
     def is_installed(self):
+        """True if the package is installed locally; otherwise, False"""
         installer_kwargs = self.installer_kwargs
         if self.import_name in config._stdlib_modules:
             # smuggled module is part of standard library
             return True
-        elif (
+        if (
                 installer_kwargs.get('force_reinstall') or
                 installer_kwargs.get('ignore_installed') or
                 installer_kwargs.get('upgrade')
         ):
             # args that trigger install regardless of installed version
             return False
-        elif self.args_str == config._smuggled.get(self.cache_key):
+        if self.args_str == config._smuggled.get(self.cache_key):
             # if the same version of the same package was smuggled from
             # the same source with all the same arguments previously in
             # the current interpreter session/notebook runtime (i.e.,
             # line is just being rerun)
             return True
-        elif '/' not in self.install_name:
+        if '/' not in self.install_name:
             # onion comment does not specify a VCS URL
             full_spec = self.install_name + self.version_spec.replace("'", "")
             try:
@@ -504,9 +496,8 @@ def is_installed(self):
                 if module_spec is None:
                     # requested package is not installed
                     return False
-                else:
-                    # requested package is a namespace package
-                    return True
+                # requested package is a namespace package
+                return True
             except (
                 # package is installed, but installed version doesn't
                 # fit requested version constraints
@@ -643,14 +634,14 @@ def parse_line(line):
 
     smuggle_funcs = []
     names_aliases = to_smuggle.split(',')
-    for na in names_aliases:
-        if ' as ' in na:
-            name, alias = na.split(' as ')
+    for n_a in names_aliases:
+        if ' as ' in n_a:
+            name, alias = n_a.split(' as ')
             name = f'"{qualname_prefix}{name}"'
             alias = f'"{alias.strip()}"'
         else:
-            name = f'"{qualname_prefix}{na}"'
-            alias = f'"{na.strip()}"' if is_from_statement else None
+            name = f'"{qualname_prefix}{n_a}"'
+            alias = f'"{n_a.strip()}"' if is_from_statement else None
 
         name = name.replace(' ', '')
         smuggle_funcs.append(f'smuggle(name={name}, as_={alias})')
@@ -721,8 +712,7 @@ def prompt_input(prompt, default=None, interrupt=None):
         except KeyboardInterrupt:
             if interrupt is not None:
                 return response_values[interrupt]
-            else:
-                raise
+            raise
         except KeyError:
             pass
 
@@ -856,7 +846,7 @@ def smuggle(
             # Also adds module (+ parents, if any) to sys.modules if not
             # already present.
             smuggled_obj = import_name(name)
-        except ModuleNotFoundError as e:
+        except ModuleNotFoundError:
             install_pkg = True
         else:
             install_pkg = False
@@ -916,7 +906,7 @@ def smuggle(
             dep_modules_old[dep_name] = sys.modules[dep_name]
             try:
                 importlib.reload(sys.modules[dep_name])
-            except (ImportError, ModuleNotFoundError, RuntimeError):
+            except (ImportError, RuntimeError):
                 # if we aren't able to reload the module, put the old
                 # version's submodules we removed back in sys.modules
                 # for now and prepare to show a warning post-execution.
diff --git a/davos/core/exceptions.py b/davos/core/exceptions.py
index 0e576522..5b4912bc 100644
--- a/davos/core/exceptions.py
+++ b/davos/core/exceptions.py
@@ -15,19 +15,17 @@
 ]
 
 
-import IPython
-
 from argparse import ArgumentError
 from shutil import get_terminal_size
 from subprocess import CalledProcessError
 from textwrap import fill, indent
 
+import IPython
+
 
 class DavosError(Exception):
     """Base class for all `davos` library exceptions."""
 
-    pass
-
 
 class DavosConfigError(DavosError):
     """Class for errors related to the `davos.config` object."""
@@ -68,8 +66,7 @@ def __init__(
             self,
             msg=None,
             target_text=None,
-            target_offset=1,
-            *args
+            target_offset=1
     ):
         """
         Parameters
@@ -87,9 +84,6 @@ def __init__(
             The (*1-indexed*) column offset from the beginning of
             `target_text` where the error occurred. Defaults to `1` (the
             first character in `target_text`).
-        *args : tuple, optional
-            Additional arguments forwarded to the `SyntaxError`
-            constructor.
         """
         # note: flot is a 4-tuple of (filename, lineno, offset, text)
         # passed to the SyntaxError constructor.
@@ -117,14 +111,12 @@ def __init__(
             # leave lineno as None so IPython will fill it in as
             # "<ipython-input-...>"
             flot = (None, lineno, offset, target_text)
-        super().__init__(msg, flot, *args)
+        super().__init__(msg, flot)
 
 
 class OnionParserError(DavosParserError):
     """Class for errors related to parsing the onion comment syntax."""
 
-    pass
-
 
 class OnionArgumentError(ArgumentError, OnionParserError):
     """
@@ -138,7 +130,7 @@ class OnionArgumentError(ArgumentError, OnionParserError):
     be expected to support attributes defined on both parents.
     """
 
-    def __init__(self, msg, argument=None, onion_txt=None, *args):
+    def __init__(self, msg, argument=None, onion_txt=None):
         """
         Parameters
         ----------
@@ -154,9 +146,6 @@ def __init__(self, msg, argument=None, onion_txt=None, *args):
             `# <installer>:` removed). If `None` (default), the error
             will not be displayed in Python's `SyntaxError`-specific
             traceback format.
-        args : tuple, optional
-            Additional arguments forwarded to the `DavosParserError`
-            constructor.
         """
         if (
                 msg is not None and
@@ -183,7 +172,7 @@ def __init__(self, msg, argument=None, onion_txt=None, *args):
         # than just running through the MRO via a call to super()
         ArgumentError.__init__(self, argument=None, message=msg)
         OnionParserError.__init__(self, msg=msg, target_text=onion_txt,
-                                  target_offset=target_offset, *args)
+                                  target_offset=target_offset)
         self.argument_name = argument
 
 
@@ -199,20 +188,14 @@ class ParserNotImplementedError(OnionParserError, NotImplementedError):
     comment) whose command line parser has not yet been added to `davos`
     """
 
-    pass
-
 
 class SmugglerError(DavosError):
     """Base class for errors raised during the smuggle phase"""
 
-    pass
-
 
 class TheNightIsDarkAndFullOfTerrors(SmugglerError):
     """A little Easter egg for if someone tries to `smuggle davos`"""
 
-    pass
-
 
 class InstallerError(SmugglerError, CalledProcessError):
     """
diff --git a/davos/core/exceptions.pyi b/davos/core/exceptions.pyi
index 521554ff..e3374eef 100644
--- a/davos/core/exceptions.pyi
+++ b/davos/core/exceptions.pyi
@@ -1,6 +1,6 @@
 from argparse import ArgumentError
 from subprocess import CalledProcessError
-from typing import Literal, Optional, overload, Union
+from typing import Literal, Optional
 
 __all__: list[
     Literal[
@@ -27,8 +27,7 @@ class DavosParserError(SyntaxError, DavosError):
             self,
             msg: Optional[str] = ...,
             target_text: Optional[str] = ...,
-            target_offset: int = ...,
-            *args: str
+            target_offset: int = ...
     ) -> None: ...
 
 class OnionParserError(DavosParserError): ...
@@ -38,8 +37,7 @@ class OnionArgumentError(ArgumentError, OnionParserError):
             self,
             msg: Optional[str] = ...,
             argument: Optional[str] = ...,
-            onion_txt: Optional[str] = ...,
-            *args: str
+            onion_txt: Optional[str] = ...
     ) -> None: ...
 
 class ParserNotImplementedError(OnionParserError, NotImplementedError): ...
diff --git a/davos/core/parsers.py b/davos/core/parsers.py
index 1b06a9e3..01dc7623 100644
--- a/davos/core/parsers.py
+++ b/davos/core/parsers.py
@@ -16,6 +16,7 @@
     ArgumentParser,
     SUPPRESS
 )
+from textwrap import dedent, indent
 
 from davos.core.exceptions import OnionArgumentError
 
@@ -50,6 +51,7 @@ class OnionParser(ArgumentParser):
     arguments are consistent across versions.
     """
 
+    # pylint: disable=signature-differs
     def parse_args(self, args, namespace=None):
         """
         Parse installer options as command line arguments.
@@ -69,6 +71,7 @@ def parse_args(self, args, namespace=None):
         object
             The populated object passed as `namespace`
         """
+        # pylint: disable=attribute-defined-outside-init
         self._args = ' '.join(args)
         try:
             ns, extras = super().parse_known_args(args=args,
@@ -76,13 +79,12 @@ def parse_args(self, args, namespace=None):
         except (ArgumentError, ArgumentTypeError) as e:
             if isinstance(e, OnionArgumentError):
                 raise
-            elif isinstance(e, ArgumentError):
+            if isinstance(e, ArgumentError):
                 raise OnionArgumentError(msg=e.message,
                                          argument=e.argument_name,
                                          onion_txt=self._args) from None
-            else:
-                raise OnionArgumentError(msg=e.args[0],
-                                         onion_txt=self._args) from None
+            raise OnionArgumentError(msg=e.args[0],
+                                     onion_txt=self._args) from None
         else:
             if extras:
                 msg = f"Unrecognized arguments: {' '.join(extras)}"
@@ -113,11 +115,10 @@ def error(self, message):
             The error message to be displayed for the raised exception.
         """
         if sys.exc_info()[1] is not None:
-            raise
-        else:
-            if message == 'one of the arguments -e/--editable is required':
-                message = 'Onion comment must specify a package name'
-            raise OnionArgumentError(msg=message, onion_txt=self._args)
+            raise    # pylint: disable=misplaced-bare-raise
+        if message == 'one of the arguments -e/--editable is required':
+            message = 'Onion comment must specify a package name'
+        raise OnionArgumentError(msg=message, onion_txt=self._args)
 
 
 class EditableAction(Action):
@@ -145,6 +146,7 @@ class EditableAction(Action):
            `editable` will always be a `bool`.
     """
 
+    # noinspection PyShadowingBuiltins
     def __init__(
             self,
             option_strings,
@@ -203,6 +205,7 @@ class SubtractAction(Action):
     the net value of the two.
     """
 
+    # noinspection PyShadowingBuiltins
     def __init__(
             self,
             option_strings,
@@ -239,12 +242,14 @@ def __call__(self, parser, namespace, values, option_string=None):
 # does not include usage for `pip install [options] -r
 # <requirements file> [package-index-options] ...` since it's not
 # relevant to `davos` usage
-_pip_install_usage = '\n  '.join("""
-pip install [options] <requirement specifier> [package-index-options] ...
-pip install [options] [-e] <vcs project url> ...
-pip install [options] [-e] <local project path> ...
-pip install [options] <archive url/path> ...
-""".splitlines())
+_pip_install_usage = indent(    # noqa: E124 pylint: disable=invalid-name
+    dedent("""\
+        pip install [options] <requirement specifier> [package-index-options] ...
+        pip install [options] [-e] <vcs project url> ...
+        pip install [options] [-e] <local project path> ...
+        pip install [options] <archive url/path> ..."""
+    ), '  '
+)
 
 pip_parser = OnionParser(usage=_pip_install_usage,
                          add_help=False,
diff --git a/davos/core/parsers.pyi b/davos/core/parsers.pyi
index 5dfd77c9..26f29cf1 100644
--- a/davos/core/parsers.pyi
+++ b/davos/core/parsers.pyi
@@ -7,8 +7,8 @@ __all__ = list[Literal['EditableAction', 'OnionParser', 'pip_parser', 'SubtractA
 class OnionParser(ArgumentParser):
     _args: Optional[str]
     def parse_args(                                              # type: ignore
-            self, 
-            args: Sequence[str], 
+            self,
+            args: Sequence[str],
             namespace: Optional[Namespace] = ...
     ) -> Namespace: ...
     def error(self, message: str) -> NoReturn: ...
@@ -48,4 +48,4 @@ class SubtractAction(Action):
     ) -> None: ...
 
 _pip_install_usage: list[str]
-pip_parser: OnionParser
\ No newline at end of file
+pip_parser: OnionParser
diff --git a/davos/core/regexps.py b/davos/core/regexps.py
index 6e99ddf9..6bd02eb2 100644
--- a/davos/core/regexps.py
+++ b/davos/core/regexps.py
@@ -10,7 +10,7 @@
 import re
 
 
-_name_re = r'[a-zA-Z]\w*'
+_name_re = r'[a-zA-Z_]\w*'    # pylint: disable=invalid-name
 
 _smuggle_subexprs = {
     'name_re': _name_re,
@@ -20,10 +20,11 @@
     'comment_re': r'(?m:\#+.*$)'
 }
 
-pip_installed_pkgs_regex = re.compile("^Successfully installed (.*)$", re.MULTILINE)
+pip_installed_pkgs_regex = re.compile("^Successfully installed (.*)$",
+                                      re.MULTILINE)
 
-
-smuggle_statement_regex = re.compile((
+# pylint: disable=line-too-long, trailing-whitespace
+smuggle_statement_regex = re.compile((    # noqa: E131
     r'^\s*'                                                               # match only if statement is first non-whitespace chars
     r'(?P<FULL_CMD>'                                                      # capture full text of command in named group
         r'(?:'                                                            # first valid syntax:
@@ -115,19 +116,19 @@
 ).format_map(_smuggle_subexprs))
 
 # Condensed, fully substituted regex:
-# ^\s*(?P<FULL_CMD>(?:smuggle +[a-zA-Z]\w*(?: *\. *[a-zA-Z]\w*)*(?: +as
-# +[a-zA-Z]\w*)?(?: *, *[a-zA-Z]\w*(?: *\. *[a-zA-Z]\w*)*(?: +as +[a-zA-
-# Z]\w*)?)*(?P<SEMICOLON_SEP>(?= *; *(?:smuggle|from)))?(?(SEMICOLON_SEP
-# )|(?: *(?=\#+ *(?:pip|conda) *: *[^#\n ].+?(?= +\#| *\n| *$))(?P<ONION
-# >\#+ *(?:pip|conda) *: *[^#\n ].+?(?= +\#| *\n| *$))?)?))|(?:from *[a-
-# zA-Z]\w*(?: *\. *[a-zA-Z]\w*)* +smuggle +(?P<OPEN_PARENS>\()?(?(OPEN_P
-# ARENS)(?: *(?:[a-zA-Z]\w*(?: +as +[a-zA-Z]\w*)? *(?:, *[a-zA-Z]\w*(?:
-# +as +[a-zA-Z]\w*)? *)*,? *)?(?:(?P<FROM_ONION_1>\#+ *(?:pip|conda) *:
-# *[^#\n ].+?(?= +\#| *\n| *$)) *(?m:\#+.*$)?|(?m:\#+.*$)|(?m:$)|(?P<CLO
-# SE_PARENS_FIRSTLINE>\)))(?(CLOSE_PARENS_FIRSTLINE)|(?:\s*(?:[a-zA-Z]\w
-# *(?: +as +[a-zA-Z]\w*)? *(?:, *[a-zA-Z]\w*(?: +as +[a-zA-Z]\w*)? *)*[^
-# )\n]*| *(?m:\#+.*$)|\n *))*\)))|[a-zA-Z]\w*(?: +as +[a-zA-Z]\w*)?(?: *
-# , *[a-zA-Z]\w*(?: +as +[a-zA-Z]\w*)?)*)(?P<FROM_SEMICOLON_SEP>(?= *; *
-# (?:smuggle|from)))?(?(FROM_SEMICOLON_SEP)|(?(FROM_ONION_1)|(?: *(?=\#+
-#  *(?:pip|conda) *: *[^#\n ].+?(?= +\#| *\n| *$))(?P<FROM_ONION>\#+ *(?
-# :pip|conda) *: *[^#\n ].+?(?= +\#| *\n| *$)))?))))
+# ^\s*(?P<FULL_CMD>(?:smuggle +[a-zA-Z_]\w*(?: *\. *[a-zA-Z_]\w*)*(?: +a
+# s +[a-zA-Z_]\w*)?(?: *, *[a-zA-Z_]\w*(?: *\. *[a-zA-Z_]\w*)*(?: +as +[
+# a-zA-Z_]\w*)?)*(?P<SEMICOLON_SEP>(?= *; *(?:smuggle|from)))?(?(SEMICOL
+# ON_SEP)|(?: *(?=\# *(?:pip|conda) *: *[^#\n ].+?(?= +\#| *\n| *$))(?P<
+# ONION>\# *(?:pip|conda) *: *[^#\n ].+?(?= +\#| *\n| *$))?)?))|(?:from
+# *[a-zA-Z_]\w*(?: *\. *[a-zA-Z_]\w*)* +smuggle +(?P<OPEN_PARENS>\()?(?(
+# OPEN_PARENS)(?: *(?:[a-zA-Z_]\w*(?: +as +[a-zA-Z_]\w*)? *(?:, *[a-zA-Z
+# _]\w*(?: +as +[a-zA-Z_]\w*)? *)*,? *)?(?:(?P<FROM_ONION_1>\# *(?:pip|c
+# onda) *: *[^#\n ].+?(?= +\#| *\n| *$)) *(?m:\#+.*$)?|(?m:\#+.*$)|(?m:$
+# )|(?P<CLOSE_PARENS_FIRSTLINE>\)))(?(CLOSE_PARENS_FIRSTLINE)|(?:\s*(?:[
+# a-zA-Z_]\w*(?: +as +[a-zA-Z_]\w*)? *(?:, *[a-zA-Z_]\w*(?: +as +[a-zA-Z
+# _]\w*)? *)*[^)\n]*| *(?m:\#+.*$)|\n *))*\)))|[a-zA-Z_]\w*(?: +as +[a-z
+# A-Z_]\w*)?(?: *, *[a-zA-Z_]\w*(?: +as +[a-zA-Z_]\w*)?)*)(?P<FROM_SEMIC
+# OLON_SEP>(?= *; *(?:smuggle|from)))?(?(FROM_SEMICOLON_SEP)|(?(FROM_ONI
+# ON_1)|(?: *(?=\# *(?:pip|conda) *: *[^#\n ].+?(?= +\#| *\n| *$))(?P<FR
+# OM_ONION>\# *(?:pip|conda) *: *[^#\n ].+?(?= +\#| *\n| *$)))?))))
diff --git a/davos/core/regexps.pyi b/davos/core/regexps.pyi
index 64e88226..1bde6b1b 100644
--- a/davos/core/regexps.pyi
+++ b/davos/core/regexps.pyi
@@ -3,15 +3,15 @@ from typing import Literal, TypedDict
 
 __all__: list[Literal['pip_installed_pkgs_regex', 'smuggle_statement_regex']]
 
-_name_re: Literal[r'[a-zA-Z]\w*']
+_name_re: Literal[r'[a-zA-Z_]\w*']
 
 # noinspection PyPep8Naming
 class _smuggle_subexprs(TypedDict):
-    as_re: Literal[r' +as +[a-zA-Z]\w*']
+    as_re: Literal[r' +as +[a-zA-Z_]\w*']
     comment_re: Literal[r'(?m:\#+.*$)']
-    name_re: Literal[r'[a-zA-Z]\w*']
+    name_re: Literal[r'[a-zA-Z_]\w*']
     onion_re: Literal[r'\# *(?:pip|conda) *: *[^#\n ].+?(?= +\#| *\n| *$)']
-    qualname_re: Literal[r'[a-zA-Z]\w*(?: *\. *[a-zA-Z]\w*)*']
+    qualname_re: Literal[r'[a-zA-Z_]\w*(?: *\. *[a-zA-Z_]\w*)*']
 
 pip_installed_pkgs_regex: Pattern[str]
 smuggle_statement_regex: Pattern[str]
diff --git a/davos/implementations/__init__.py b/davos/implementations/__init__.py
index cc28a554..0c69be85 100644
--- a/davos/implementations/__init__.py
+++ b/davos/implementations/__init__.py
@@ -48,7 +48,9 @@
 
 import_environment = config.environment
 
+
 if import_environment == 'Python':
+    # noinspection PyUnresolvedReferences
     from davos.implementations.python import (
         _activate_helper,
         _check_conda_avail_helper,
@@ -59,6 +61,7 @@
         prompt_restart_rerun_buttons
     )
 else:
+    # noinspection PyUnresolvedReferences
     from davos.implementations.ipython_common import (
         _check_conda_avail_helper,
         _run_shell_command_helper,
@@ -94,7 +97,6 @@
                 prompt_restart_rerun_buttons
             )
 
-
 from davos.core.core import check_conda, smuggle, parse_line
 
 
@@ -114,6 +116,9 @@
 #     `check_conda`) which needs to be imported after
 #     implementation-specific functions are set here.
 
+
+# pylint: disable=unused-argument
+# noinspection PyUnusedLocal
 def _active_fget(conf):
     """getter for davos.config.active"""
     return config._active
@@ -139,6 +144,7 @@ def _conda_avail_fget(conf):
     return conf._conda_avail
 
 
+# noinspection PyUnusedLocal
 def _conda_avail_fset(conf, _):
     """setter for davos.config.conda_avail"""
     raise DavosConfigError('conda_avail', 'field is read-only')
@@ -165,7 +171,7 @@ def _conda_env_fset(conf, new_env):
             "conda_env",
             "cannot set conda environment. No local conda installation found"
         )
-    elif new_env != conf._conda_env:
+    if new_env != conf._conda_env:
         if (
                 conf._conda_envs_dirs is not None and
                 new_env not in conf._conda_envs_dirs.keys()
@@ -188,6 +194,7 @@ def _conda_envs_dirs_fget(conf):
     return conf._conda_envs_dirs
 
 
+# noinspection PyUnusedLocal
 def _conda_envs_dirs_fset(conf, _):
     """setter for davos.config.conda_envs_dirs"""
     raise DavosConfigError('conda_envs_dirs', 'field is read-only')
diff --git a/davos/implementations/__init__.pyi b/davos/implementations/__init__.pyi
index fd00abbd..5abeac61 100644
--- a/davos/implementations/__init__.pyi
+++ b/davos/implementations/__init__.pyi
@@ -2,6 +2,7 @@ from collections.abc import Callable
 from typing import Any, Literal, NoReturn, Optional, Protocol, Union
 from davos.core.config import DavosConfig, IpythonShell
 from davos.core.core import PipInstallerKwargs
+# noinspection PyUnresolvedReferences
 from davos.implementations.ipython_post7 import IPyPost7FullParserFunc
 
 __all__ = list[
diff --git a/davos/implementations/colab.py b/davos/implementations/colab.py
index baf12c92..ea04b907 100644
--- a/davos/implementations/colab.py
+++ b/davos/implementations/colab.py
@@ -4,7 +4,7 @@
 """
 
 
-__all__ = ['auto_restart_rerun', 'prompt_rerun_buttons']
+__all__ = ['auto_restart_rerun', 'prompt_restart_rerun_buttons']
 
 
 from IPython.core.display import _display_mimetype
diff --git a/davos/implementations/ipython_common.py b/davos/implementations/ipython_common.py
index 615b5f83..acf3c4d8 100644
--- a/davos/implementations/ipython_common.py
+++ b/davos/implementations/ipython_common.py
@@ -4,7 +4,6 @@
 """
 
 
-import re
 import sys
 import textwrap
 from contextlib import redirect_stdout
@@ -12,7 +11,6 @@
 from pathlib import Path
 from subprocess import CalledProcessError
 
-from IPython.core.error import UsageError
 from IPython.utils.process import system as _run_shell_cmd
 
 from davos import config
@@ -160,14 +158,18 @@ def _set_custom_showsyntaxerror():
 
     _showsyntaxerror_davos.__doc__ = new_doc
     config._ipy_showsyntaxerror_orig = ipy_shell.showsyntaxerror
-    ipy_shell.showsyntaxerror = _showsyntaxerror_davos.__get__(ipy_shell)
+    # bind function as method
+    # pylint: disable=no-value-for-parameter
+    # (pylint bug: expects __get__ method to take same args as function)
+    ipy_shell.showsyntaxerror = _showsyntaxerror_davos.__get__(ipy_shell,
+                                                               type(ipy_shell))
 
 
 # noinspection PyUnusedLocal
 def _showsyntaxerror_davos(
         ipy_shell,
         filename=None,
-        running_compiled_code=False
+        running_compiled_code=False    # pylint: disable=unused-argument
 ):
     """
     Show `davos` library `SyntaxError` subclasses with full tracebacks.
@@ -227,18 +229,18 @@ def _showsyntaxerror_davos(
         try:
             # noinspection PyBroadException
             try:
+                # display custom traceback, if class supports it
                 stb = value._render_traceback_()
-            except:
+            except Exception:    # pylint: disable=broad-except
                 stb = ipy_shell.InteractiveTB.structured_traceback(
                     etype, value, tb, tb_offset=ipy_shell.InteractiveTB.tb_offset
                 )
             ipy_shell._showtraceback(etype, value, stb)
             if ipy_shell.call_pdb:
                 ipy_shell.debugger(force=True)
-            return
         except KeyboardInterrupt:
             print('\n' + ipy_shell.get_exception_only(), file=sys.stderr)
-    else:
-        # original method is stored in Davos instance, but still bound
-        # IPython.core.interactiveshell.InteractiveShell instance
-        return config._ipy_showsyntaxerror_orig(filename=filename)
+        return None
+    # original method is stored in Davos instance, but still bound
+    # IPython.core.interactiveshell.InteractiveShell instance
+    return config._ipy_showsyntaxerror_orig(filename=filename)
diff --git a/davos/implementations/ipython_post7.py b/davos/implementations/ipython_post7.py
index aee5ece0..1f523ff0 100644
--- a/davos/implementations/ipython_post7.py
+++ b/davos/implementations/ipython_post7.py
@@ -178,7 +178,7 @@ def full_parser(lines):
 
         parsed_lines = []
         curr_buff = []
-        for ix, raw_line in enumerate(lines):
+        for raw_line in lines:
             # don't include trailing '\n'
             python_line = pyline_assembler.push(raw_line[:-1])
             if python_line is None:
diff --git a/davos/implementations/js_functions.py b/davos/implementations/js_functions.py
index bd86e9f7..cc800b00 100644
--- a/davos/implementations/js_functions.py
+++ b/davos/implementations/js_functions.py
@@ -48,6 +48,7 @@ class DotDict(dict):
     __setattr__ = dict.__setitem__
 
     # noinspection PyMissingConstructor
+    # pylint: disable=super-init-not-called
     def __init__(self, d):
         """
         Parameters
diff --git a/davos/implementations/jupyter.py b/davos/implementations/jupyter.py
index 1b4d7c32..e22e4238 100644
--- a/davos/implementations/jupyter.py
+++ b/davos/implementations/jupyter.py
@@ -165,8 +165,7 @@ def prompt_restart_rerun_buttons(pkgs):
         displayButtonPrompt(buttonArgs, true);
     """)
 
-    # noinspection PyUnresolvedReferences
-    # (function exists globally when imported into IPython context)
+    # get_ipython() exists globally when imported into IPython context
     kernel = get_ipython().kernel
     stdin_sock = kernel.stdin_socket
 
@@ -187,8 +186,7 @@ def prompt_restart_rerun_buttons(pkgs):
             # (dynamically imported names not included in stub files)
             if e.errno == zmq.EAGAIN:
                 break
-            else:
-                raise
+            raise
 
     # noinspection PyTypeChecker
     display(Javascript(display_button_prompt_full))
@@ -205,19 +203,18 @@ def prompt_restart_rerun_buttons(pkgs):
                 ident, reply = kernel.session.recv(stdin_sock)
                 if ident is not None or reply is not None:
                     break
-        except Exception as e:
+        except Exception as e:    # pylint: disable=broad-except
             if isinstance(e, KeyboardInterrupt):
                 # re-raise KeyboardInterrupt with simplified traceback
                 # (excludes some convoluted calls to internal
                 # IPython/zmq machinery)
                 raise KeyboardInterrupt("Interrupted by user") from None
-            else:
-                kernel.log.warning("Invalid Message:", exc_info=True)
+            kernel.log.warning("Invalid Message:", exc_info=True)
 
     # noinspection PyBroadException
     try:
         value = py3compat.unicode_to_str(reply['content']['value'])
-    except:
+    except Exception:    # pylint: disable=broad-except
         if ipykernel.version_info[0] >= 6:
             _parent_header = kernel._parent_ident['shell']
         else:
diff --git a/davos/implementations/jupyter.pyi b/davos/implementations/jupyter.pyi
index fbd79091..79f6b01a 100644
--- a/davos/implementations/jupyter.pyi
+++ b/davos/implementations/jupyter.pyi
@@ -1,4 +1,4 @@
-from typing import Literal, NoReturn, Union
+from typing import Literal, NoReturn
 
 __all__ = list[Literal['auto_restart_rerun', 'prompt_restart_rerun_buttons']]
 
diff --git a/davos/implementations/python.py b/davos/implementations/python.py
index cc94fd56..eb8051d7 100644
--- a/davos/implementations/python.py
+++ b/davos/implementations/python.py
@@ -22,6 +22,7 @@
 from subprocess import CalledProcessError, PIPE, Popen
 
 
+# noinspection PyUnusedLocal
 def _activate_helper(smuggle_func, parser_func):
     """
     Pure Python implementation of `_activate_helper`.
@@ -84,6 +85,7 @@ def _check_conda_avail_helper():
     return conda_list_output.getvalue()
 
 
+# noinspection PyUnusedLocal
 def _deactivate_helper(smuggle_func, parser_func):
     """
     Pure Python implementation of `_activate_helper`.
@@ -133,7 +135,9 @@ def _run_shell_command_helper(command):
         If the command returned a non-zero exit status.
     """
     cmd = shlex.split(command)
-    process = Popen(cmd, stdout=PIPE, stderr=PIPE,
+    process = Popen(cmd,    # pylint: disable=consider-using-with
+                    stdout=PIPE,
+                    stderr=PIPE,
                     encoding=locale.getpreferredencoding())
     try:
         while True:
@@ -151,6 +155,7 @@ def _run_shell_command_helper(command):
         raise
 
 
+# noinspection PyUnusedLocal
 def auto_restart_rerun(pkgs):
     """
     Pure Python implementation of `auto_restart_rerun`.
@@ -175,6 +180,7 @@ def auto_restart_rerun(pkgs):
     )
 
 
+# noinspection PyUnusedLocal
 def generate_parser_func(line_parser):
     """
     Pure Python implementation of `generate_parser_func`.
@@ -198,6 +204,7 @@ def generate_parser_func(line_parser):
     )
 
 
+# noinspection PyUnusedLocal
 def prompt_restart_rerun_buttons(pkgs):
     """
     Pure Python implementation of `prompt_restart_rerun_buttons`.
diff --git a/pyproject.toml b/pyproject.toml
index b36b7869..113b7b98 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,10 +1,77 @@
 [build-system]
-requires = [
-    "setuptools>=46.4",
-    "wheel"
-]
+requires = ["setuptools>=46.4", "wheel"]
 build-backend = "setuptools.build_meta"
 
+# ======================================
+
+[tool.mypy]
+python_version = "3.9"
+disable_error_code = "override"
+disallow_any_expr = true
+disallow_untyped_calls = true
+disallow_untyped_defs = true
+disallow_incomplete_defs = true
+disallow_untyped_decorators = true
+no_implicit_optional = true
+warn_redundant_casts = true
+warn_unused_ignores = true
+warn_return_any = true
+warn_unreachable = true
+show_error_codes = true
+show_absolute_path = true
+
+# ======================================
+[tool.pylint.master]
+load-plugins = ["pylint.extensions.overlapping_exceptions"]
+
+[tool.pylint.basic]
+good-names = ["e", "i", "ix", "k", "ns", "p", "tb", "v"]
+
+[tool.pylint.messages_control]
+disable = [
+    "cyclic-import",
+    "fixme",
+    "import-outside-toplevel",
+    "too-many-ancestors",
+    "too-many-arguments",
+    "too-many-branches",
+    "too-many-locals",
+    "too-many-return-statements",
+    "too-many-statements",
+    "wrong-import-position"
+]
+
+[tool.pylint.classes]
+exclude-protected = [
+    # davos.core.config.DavosConfig attributes
+    "_active",
+    "_conda_avail",
+    "_conda_env",
+    "_conda_envs_dirs",
+    "_ipy_showsyntaxerror_orig",
+    "_ipython_shell",
+    "_pip_executable",
+    "_smuggled",
+    "_stdlib_modules",
+    # IPython.core.interactiveshell.InteractiveShell methods
+    "_get_exc_info",
+    "_showtraceback",
+    # IPython custom display method for Exception classes
+    "_render_traceback_",
+    # ipykernel.ipkernel.IPythonKernel attribute
+    "_parent_ident"
+]
+
+[tool.pylint.design]
+max-attributes = 15
+
+[tool.pylint.typecheck]
+generated-members = ["zmq.EAGAIN", "NOBLOCK"]
+
+[tool.pylint.variables]
+additional-builtins = ["get_ipython"]
+allowed-redefined-builtins = ["help"]
+
 [tool.pytest.ini_options]
 addopts = "--capture=no --strict-markers --verbose"
 markers = [
diff --git a/setup.cfg b/setup.cfg
index b76f6616..b435f636 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -30,7 +30,8 @@ classifiers =
 
 [options]
 python_requires = >=3.6
-install_requires = packaging
+# require PEP 517-compatible pip version
+install_requires = packaging; setuptools
 setup_requires = setuptools>=42.0.2
 packages = find:
 include_package_data = true
@@ -43,7 +44,7 @@ tests =
     IPython>=7.3.0;python_version>="3.8"
     IPython>=5.5.0
     ipykernel>=5.0.0
-    mypy>=0.812
+    mypy==0.910
     pytest==6.2
     requests
     selenium>=3.141
@@ -55,20 +56,3 @@ tests =
 [bdist_wheel]
 # not compatible with Python 2.x
 universal = 0
-
-[mypy]
-python_version = 3.9
-disable_error_code = override
-disallow_any_expr = true
-disallow_any_generics = true
-disallow_untyped_calls = true
-disallow_untyped_defs = true
-disallow_incomplete_defs = true
-disallow_untyped_decorators = true
-no_implicit_optional = true
-warn_redundant_casts = true
-warn_unused_ignores = true
-warn_return_any = true
-warn_unreachable = true
-show_error_codes = true
-show_absolute_path = true
diff --git a/setup.py b/setup.py
deleted file mode 100644
index 83182b7c..00000000
--- a/setup.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# PEP 517/pyproject.toml package spec doesn't yet support editable
-# installation, so still need setup.py for development
-# (https://github.com/pypa/pip/pull/6370#issuecomment-479938348)
-
-
-from setuptools import setup
-
-
-setup()


Test patch:
diff --git a/tests/test_regexps.ipynb b/tests/test_regexps.ipynb
index f84866d2..adfaa593 100644
--- a/tests/test_regexps.ipynb
+++ b/tests/test_regexps.ipynb
@@ -1593,6 +1593,43 @@
     "    )"
    ]
   },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "ExecuteTime": {
+     "end_time": "2021-08-18T03:43:33.782941Z",
+     "start_time": "2021-08-18T03:43:33.775349Z"
+    }
+   },
+   "outputs": [],
+   "source": [
+    "def test_smuggle_from_regex_underscores():\n",
+    "    \"\"\"\n",
+    "    should match package, module, and alias names that start with, end \n",
+    "    with, and contain underscores\n",
+    "    \"\"\"\n",
+    "    line = 'from _fo_o_._b_ar_ smuggle _b_az_ as __qu_ux__'\n",
+    "    expected_groupdict = {\n",
+    "        'FULL_CMD': line,\n",
+    "        'SEMICOLON_SEP': None,\n",
+    "        'ONION': None,\n",
+    "        'OPEN_PARENS': None,\n",
+    "        'FROM_ONION_1': None,\n",
+    "        'CLOSE_PARENS_FIRSTLINE': None,\n",
+    "        'FROM_SEMICOLON_SEP': None,\n",
+    "        'FROM_ONION': None\n",
+    "    }\n",
+    "    match = smuggle_statement_regex.match(line)\n",
+    "    assert match is not None, f\"failed to match: '{line}'\"\n",
+    "    \n",
+    "    result_groupdict = match.groupdict()\n",
+    "    assert result_groupdict == expected_groupdict, (\n",
+    "        \"bad values in match.groupdict(). Expected:\\n\"\n",
+    "        f\"{pformat(expected_groupdict)}\\nFound:\\n{pformat(result_groupdict)}\"\n",
+    "    )"
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": null,
diff --git a/tests/utils.py b/tests/utils.py
index 8f4704ac..c12d2c1b 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -60,22 +60,18 @@ class _DecoratorData(TypedDict):
 ########################################
 class DavosTestingError(Exception):
     """Base class for Davos testing-related errors"""
-    pass
 
 
 class DavosAssertionError(DavosTestingError, AssertionError):
     """Subclasses AssertionError for pytest-specific handling"""
-    pass
 
 
 class TestingEnvironmentError(DavosAssertionError, OSError):
     """Raised due to issues with the testing environment"""
-    pass
 
 
 class TestTimeoutError(DavosAssertionError):
     """Raised if a test exceeds its timeout limit"""
-    pass
 
 
 ########################################
@@ -197,7 +193,7 @@ def install_davos(
 def is_imported(pkg_name: str) -> bool:
     if pkg_name in sys.modules:
         return True
-    elif any(pkg.startswith(f'{pkg_name}.') for pkg in sys.modules):
+    if any(pkg.startswith(f'{pkg_name}.') for pkg in sys.modules):
         return True
     return False
 
@@ -429,7 +425,7 @@ def __exit__(    # noqa: E301
     ) -> bool:
         if exc_type is None:
             raise DavosAssertionError(f"DID NOT RAISE {self.expected_exceptions}")
-        elif not issubclass(exc_type, self.expected_exceptions):
+        if not issubclass(exc_type, self.expected_exceptions):
             # causes exc_value to be raised
             return False
         self.excinfo._excinfo = (exc_type, exc_value, exc_tb)