Repository: qir-alliance/pyqir (language: python)

Issue:
Choose a Python module/package hierarchy
#43 updates the PyQIR packages to use a shared namespace package `pyqir` (`pyqir.generator`, `pyqir.jit`, etc.). It also adds additional hierarchy to the generator modules, so that e.g. `SimpleModule` must be imported from `pyqir.generator.module`, instead of simply from `pyqir.generator`.

We should take another look at these two things to make sure we're following Python conventions, and are choosing the right level of organization:

1. Do we want to use namespace packages (`pyqir.generator`) or independent modules (`pyqir_generator` or `pyqirgenerator`)?
2. Do we want to expose a nested module structure within each package (`from pyqir.generator.module import SimpleModule`), a flat one (`from pyqir.generator import SimpleModule`), or both?

## Tasks

- [x] #58
- [x] Plan for namespace packages
pyqir_parser exposing too many things on import
I was working with the parser package, and saw that when you import it, it lists a lot of things that probably shouldn't.
Things like `List` and `Optional`, the underlying Rust interface (things prefixed by "Py"), and the `parser`/`pyqir_parser` also related to internals that probably shouldn't be user facing. 

The example below was generated from using this [conda env](https://github.com/crazy4pi314/qwop/blob/master/environment.yaml), and locally downloading and installing the windows wheels from the [v0.1.0a1 pre-release](https://github.com/qir-alliance/pyqir/releases/tag/v0.1.0a1) on GH.

```
import pyqir_parser as pqp
dir(pqp)

['List',
 'Optional',
 'PyQirBasicBlock',
 'PyQirConstant',
 'PyQirFunction',
 'PyQirInstruction',
 'PyQirModule',
 'PyQirOperand',
 'PyQirParameter',
 'PyQirTerminator',
 'PyQirType',
 'QirAShrInstr',
 'QirAddInstr',
 'QirAndInstr',
 'QirArrayType',
 'QirBlock',
 'QirBrTerminator',
 'QirCallInstr',
 'QirCondBrTerminator',
 'QirConstant',
 'QirDoubleConstant',
 'QirDoubleType',
 'QirFAddInstr',
 'QirFCmpInstr',
 'QirFDivInstr',
 'QirFMulInstr',
 'QirFNegInstr',
 'QirFRemInstr',
 'QirFSubInstr',
 'QirFunction',
 'QirICmpInstr',
 'QirInstr',
 'QirIntConstant',
 'QirIntegerType',
 'QirLShrInstr',
 'QirLocalOperand',
 'QirModule',
 'QirMulInstr',
 'QirNamedStructType',
 'QirNullConstant',
 'QirOpInstr',
 'QirOperand',
 'QirOrInstr',
 'QirParameter',
 'QirPhiInstr',
 'QirPointerType',
 'QirQirCallInstr',
 'QirQisCallInstr',
 'QirQubitConstant',
 'QirQubitType',
 'QirResultConstant',
 'QirResultType',
 'QirRetTerminator',
 'QirRtCallInstr',
 'QirSDivInstr',
 'QirSRemInstr',
 'QirShlInstr',
 'QirStructType',
 'QirSubInstr',
 'QirSwitchTerminator',
 'QirTerminator',
 'QirType',
 'QirUDivInstr',
 'QirURemInstr',
 'QirUnreachableTerminator',
 'QirVoidType',
 'QirXorInstr',
 'Tuple',
 '__builtins__',
 '__cached__',
 '__doc__',
 '__file__',
 '__loader__',
 '__name__',
 '__package__',
 '__path__',
 '__spec__',
 'module_from_bitcode',
 'parser',
 'pyqir_parser']
```


Discussion / hints:
Particularly for question 1, we should consider the added complexity of namespace packages and the possibility that Maturin may never fully support them (see PyO3/maturin#811).
For question 1, my vote is that namespace packages give a much more Python-ic feel to the API, especially once we release a wheel that depends on all three other wheels for ease of installation. If a user runs `pip install pyqir` or `conda install pyqir`, then `import pyqir` nicely matches that UX. 

It's worth noting that as well that namespace packages have been adopted by projects as large as the Azure SDK for Python for similar reasons.

We can at least partially separate that question from https://github.com/PyO3/maturin/issues/811, perhaps, if we're OK using workarounds like having `pyqir/generator/__init__.py` wrap a Maturin-built native module with a name like `_pyqir_internal_generator`.

For question 2, I think it tends to result in a better UX to have things closer to flat (this is even an explicit part of `import this`), such that `from pyqir.generator import SimpleModule` requires users to know less about the internal structure of `pyqir.generator` to make use of that package. There's always a trade-off, of course, such that I have often seen submodules and subpackages used as APIs grow or if a particular submodule / subpackage has a disproportionate impact on import performance. Given the size of the current API and that there's little runtime overhead to importing the whole of `pyqir.generator`, I feel like the balance tends to land more at flat than nested.
> For question 1, my vote is that namespace packages give a much more Python-ic feel to the API, especially once we release a wheel that depends on all three other wheels for ease of installation. If a user runs `pip install pyqir` or `conda install pyqir`, then `import pyqir` nicely matches that UX.

I think that to an extent this is independent from namespace packages. For example, a meta `pyqir` package could define its own `pyqir` module with an `__init__.py` like this:

```python
import pyqir_generator as generator
import pyqir_jit as jit
import pyqir_parser as parser
```

However it would mean that there's now two ways to import things if you have the metapackage installed.
> Do we want to use namespace packages (`pyqir.generator`) or independent modules (`pyqir_generator` or `pyqirgenerator`)?

I think either works, there's no specific convention that is strongly recommended as far as I know. Namespace packages are a bit more common. `pyqir.generator`, `pyqir.jit` etc. works for me.

> Do we want to expose a nested module structure within each package (from pyqir.generator.module import SimpleModule), a flat one (from pyqir.generator import SimpleModule), or both?

I also don't think there are strict rules for this. If we think `SimpleModule` is used frequently, then I would support both `from pyqir.generator.module import SimpleModule` and a more shorthand version `from pyqir.generator import SimpleModule`. If `SimpleModule` is more obscure then I think just the former is fine.
Assigning to @samarsha for a follow up. @swernli Could you weigh in here?
This is a consequence of how Python imports work... the imported module or module members are simply added to the parent module, and are treated the same as locally defined module members. It affects other Python libraries, for example you can access the imports of `http.client`:

https://github.com/python/cpython/blob/47422a852de14a8ec11d058136c7c864d2cc7fc9/Lib/http/client.py#L71-L80

```python
>>> import http.client
>>> http.client.urlsplit
<function urlsplit at 0x000002B9A2B1CB80>
>>> http.client.sys
<module 'sys' (built-in)>
>>> http.client.collections.abc
<module 'collections.abc' from 'C:\\Users\\samarsha\\scoop\\apps\\python\\current\\lib\\collections\\abc.py'>
>>> http.client.http
<module 'http' from 'C:\\Users\\samarsha\\scoop\\apps\\python\\current\\lib\\http\\__init__.py'>
>>> http.client.http.client.http.client
<module 'http.client' from 'C:\\Users\\samarsha\\scoop\\apps\\python\\current\\lib\\http\\client.py'>
```

There are ways I know of to improve this but they are fairly burdensome on the library author (us). We could require all imports to be renamed with an underscore prefix, like

```python
import foo as _foo
from bar import bar1 as _bar1, bar2 as _bar2
```

Or you could import them normally but override a module's `__dir__` and `__getattr__`, use `del` to remove the imports at the bottom of the module, or something like that.

However, it seems like a lot of (most?) Python libraries don't do anything about this. The module's documentation should be the definitive list of exports from the module that users can depend on.

Fix patch:
diff --git a/docs/api-reference/generator.md b/docs/api-reference/generator.md
index a829d6a0..18f0a00f 100644
--- a/docs/api-reference/generator.md
+++ b/docs/api-reference/generator.md
@@ -1,9 +1,41 @@
-# pyqir_generator
+# pyqir.generator
 
-## `QirBuilder` class
+## Modules
 
 ```{eval-rst}
-.. autoclass:: pyqir_generator.QirBuilder
+.. autoclass:: pyqir.generator.SimpleModule
+    :show-inheritance:
+    :members:
+    :undoc-members:
+```
+
+## Builders
+
+```{eval-rst}
+.. autoclass:: pyqir.generator.Builder
+    :show-inheritance:
+    :members:
+    :undoc-members:
+```
+
+```{eval-rst}
+.. autoclass:: pyqir.generator.BasicQisBuilder
+    :show-inheritance:
+    :members:
+    :undoc-members:
+```
+
+## Values
+
+```{eval-rst}
+.. autoclass:: pyqir.generator.Qubit
+    :show-inheritance:
+    :members:
+    :undoc-members:
+```
+
+```{eval-rst}
+.. autoclass:: pyqir.generator.Ref
     :show-inheritance:
     :members:
     :undoc-members:
diff --git a/docs/api-reference/jit.md b/docs/api-reference/jit.md
index 53c0b101..e05a7b08 100644
--- a/docs/api-reference/jit.md
+++ b/docs/api-reference/jit.md
@@ -1,9 +1,9 @@
-# pyqir_jit
+# pyqir.jit
 
 ## `NonadaptiveJit` class
 
 ```{eval-rst}
-.. autoclass:: pyqir_jit.NonadaptiveJit
+.. autoclass:: pyqir.jit.NonadaptiveJit
     :show-inheritance:
     :members:
     :undoc-members:
@@ -12,7 +12,7 @@
 ## `GateSet` class
 
 ```{eval-rst}
-.. autoclass:: pyqir_jit.GateSet
+.. autoclass:: pyqir.jit.GateSet
     :show-inheritance:
     :members:
     :undoc-members:
@@ -21,7 +21,7 @@
 ## `GateLogger` class
 
 ```{eval-rst}
-.. autoclass:: pyqir_jit.GateLogger
+.. autoclass:: pyqir.jit.GateLogger
     :show-inheritance:
     :members:
     :undoc-members:
diff --git a/docs/api-reference/parser.md b/docs/api-reference/parser.md
index 3d832947..e15630f6 100644
--- a/docs/api-reference/parser.md
+++ b/docs/api-reference/parser.md
@@ -1,65 +1,65 @@
-# pyqir_parser
+# pyqir.parser
 
 ## Classes representing QIR types
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirType
+.. autoclass:: pyqir.parser.QirType
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirVoidType
+.. autoclass:: pyqir.parser.QirVoidType
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirIntegerType
+.. autoclass:: pyqir.parser.QirIntegerType
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirPointerType
+.. autoclass:: pyqir.parser.QirPointerType
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirDoubleType
+.. autoclass:: pyqir.parser.QirDoubleType
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirArrayType
+.. autoclass:: pyqir.parser.QirArrayType
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirStructType
+.. autoclass:: pyqir.parser.QirStructType
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirNamedStructType
+.. autoclass:: pyqir.parser.QirNamedStructType
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirResultType
+.. autoclass:: pyqir.parser.QirResultType
     :show-inheritance:
     :members:
     :undoc-members:
@@ -68,49 +68,49 @@
 ## Classes representing QIR operands
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirOperand
+.. autoclass:: pyqir.parser.QirOperand
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirLocalOperand
+.. autoclass:: pyqir.parser.QirLocalOperand
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirIntConstant
+.. autoclass:: pyqir.parser.QirIntConstant
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirDoubleConstant
+.. autoclass:: pyqir.parser.QirDoubleConstant
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirNullConstant
+.. autoclass:: pyqir.parser.QirNullConstant
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirQubitConstant
+.. autoclass:: pyqir.parser.QirQubitConstant
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirResultConstant
+.. autoclass:: pyqir.parser.QirResultConstant
     :show-inheritance:
     :members:
     :undoc-members:
@@ -119,42 +119,42 @@
 ## Classes representing basic block terminators
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirTerminator
+.. autoclass:: pyqir.parser.QirTerminator
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirRetTerminator
+.. autoclass:: pyqir.parser.QirRetTerminator
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirBrTerminator
+.. autoclass:: pyqir.parser.QirBrTerminator
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirCondBrTerminator
+.. autoclass:: pyqir.parser.QirCondBrTerminator
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirSwitchTerminator
+.. autoclass:: pyqir.parser.QirSwitchTerminator
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirUnreachableTerminator
+.. autoclass:: pyqir.parser.QirUnreachableTerminator
     :show-inheritance:
     :members:
     :undoc-members:
@@ -163,189 +163,189 @@
 ## Classes representing QIR instructions
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirInstr
+.. autoclass:: pyqir.parser.QirInstr
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirQisCallInstr
+.. autoclass:: pyqir.parser.QirQisCallInstr
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirRtCallInstr
+.. autoclass:: pyqir.parser.QirRtCallInstr
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirQirCallInstr
+.. autoclass:: pyqir.parser.QirQirCallInstr
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirCallInstr
+.. autoclass:: pyqir.parser.QirCallInstr
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirAddInstr
+.. autoclass:: pyqir.parser.QirAddInstr
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirSubInstr
+.. autoclass:: pyqir.parser.QirSubInstr
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirMulInstr
+.. autoclass:: pyqir.parser.QirMulInstr
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirUDivInstr
+.. autoclass:: pyqir.parser.QirUDivInstr
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirSDivInstr
+.. autoclass:: pyqir.parser.QirSDivInstr
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirURemInstr
+.. autoclass:: pyqir.parser.QirURemInstr
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirSRemInstr
+.. autoclass:: pyqir.parser.QirSRemInstr
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirAndInstr
+.. autoclass:: pyqir.parser.QirAndInstr
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirOrInstr
+.. autoclass:: pyqir.parser.QirOrInstr
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirXorInstr
+.. autoclass:: pyqir.parser.QirXorInstr
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirShlInstr
+.. autoclass:: pyqir.parser.QirShlInstr
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirLShrInstr
+.. autoclass:: pyqir.parser.QirLShrInstr
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirAShrInstr
+.. autoclass:: pyqir.parser.QirAShrInstr
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirFAddInstr
+.. autoclass:: pyqir.parser.QirFAddInstr
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirFSubInstr
+.. autoclass:: pyqir.parser.QirFSubInstr
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirFMulInstr
+.. autoclass:: pyqir.parser.QirFMulInstr
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirFDivInstr
+.. autoclass:: pyqir.parser.QirFDivInstr
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirFRemInstr
+.. autoclass:: pyqir.parser.QirFRemInstr
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirFNegInstr
+.. autoclass:: pyqir.parser.QirFNegInstr
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirICmpInstr
+.. autoclass:: pyqir.parser.QirICmpInstr
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirFCmpInstr
+.. autoclass:: pyqir.parser.QirFCmpInstr
     :show-inheritance:
     :members:
     :undoc-members:
 ```
 
 ```{eval-rst}
-.. autoclass:: pyqir_parser.QirPhiInstr
+.. autoclass:: pyqir.parser.QirPhiInstr
     :show-inheritance:
     :members:
     :undoc-members:
diff --git a/examples/generator/bell_pair.py b/examples/generator/bell_pair.py
index 92c1b30e..308a48a6 100644
--- a/examples/generator/bell_pair.py
+++ b/examples/generator/bell_pair.py
@@ -3,8 +3,7 @@
 # Copyright (c) Microsoft Corporation.
 # Licensed under the MIT License.
 
-from pyqir.generator.module import SimpleModule
-from pyqir.generator.qis import BasicQisBuilder
+from pyqir.generator import BasicQisBuilder, SimpleModule
 
 # This module creates a Bell pair and returns the result of measuring each
 # qubit.
diff --git a/examples/generator/mock_to_qir.py b/examples/generator/mock_to_qir.py
index 31d0b95a..d99951da 100644
--- a/examples/generator/mock_to_qir.py
+++ b/examples/generator/mock_to_qir.py
@@ -9,9 +9,7 @@
 from mock_language.MockLanguageParser import MockLanguageParser
 from mock_language.MockLanguageListener import MockLanguageListener
 from pathlib import Path
-from pyqir.generator.module import SimpleModule
-from pyqir.generator.qis import BasicQisBuilder
-from pyqir.generator.value import Qubit, Ref
+from pyqir.generator import BasicQisBuilder, Qubit, Ref, SimpleModule
 
 
 class QirGenerator(MockLanguageListener):
diff --git a/examples/jit/bernstein_vazirani.py b/examples/jit/bernstein_vazirani.py
index cd67b2fa..f77ae444 100644
--- a/examples/jit/bernstein_vazirani.py
+++ b/examples/jit/bernstein_vazirani.py
@@ -3,11 +3,9 @@
 # Copyright (c) Microsoft Corporation.
 # Licensed under the MIT License.
 
-from pyqir.jit.nonadaptivejit import NonadaptiveJit
-from pyqir.jit.gatelogger import GateLogger
-
-from pathlib import Path
 import os
+from pathlib import Path
+from pyqir.jit import GateLogger, NonadaptiveJit
 
 path = Path(__file__).parent
 file = os.path.join(path, "bernstein_vazirani.bc")
diff --git a/pyqir-generator/pyqir/generator/__init__.py b/pyqir-generator/pyqir/generator/__init__.py
index 59e481eb..028a2904 100644
--- a/pyqir-generator/pyqir/generator/__init__.py
+++ b/pyqir-generator/pyqir/generator/__init__.py
@@ -1,2 +1,10 @@
 # Copyright (c) Microsoft Corporation.
 # Licensed under the MIT License.
+
+from pyqir.generator._native import (
+    BasicQisBuilder as BasicQisBuilder,
+    Builder as Builder,
+    Qubit as Qubit,
+    Ref as Ref,
+    SimpleModule as SimpleModule,
+)
diff --git a/pyqir-generator/pyqir/generator/qis.pyi b/pyqir-generator/pyqir/generator/_native.pyi
similarity index 70%
rename from pyqir-generator/pyqir/generator/qis.pyi
rename to pyqir-generator/pyqir/generator/_native.pyi
index 8bb89ad4..2621983a 100644
--- a/pyqir-generator/pyqir/generator/qis.pyi
+++ b/pyqir-generator/pyqir/generator/_native.pyi
@@ -1,9 +1,73 @@
 # Copyright (c) Microsoft Corporation.
 # Licensed under the MIT License.
 
-from typing import Callable
-from pyqir.generator.builder import Builder
-from pyqir.generator.value import Qubit, Ref
+from typing import Callable, Tuple
+
+
+class Qubit:
+    """A qubit identifier."""
+    ...
+
+
+class Ref:
+    """A reference to an element in memory."""
+    ...
+
+
+class Builder:
+    """An instruction builder."""
+    ...
+
+
+class SimpleModule:
+    """
+    A simple module represents an executable QIR program with these
+    restrictions:
+
+    - There is one global quantum register and one global classical register.
+      Both are allocated automatically with a fixed size before the program
+      starts.
+    - There is only a single function that runs as the entry point.
+    """
+
+    def __init__(
+        self,
+        name: str,
+        num_qubits: int,
+        num_results: int,
+    ) -> None:
+        """
+        Initializes the module with a name and the number of qubits and results
+        in the quantum and classical registers.
+
+        :param name: The name of the module.
+        :param num_qubits: The size of the global quantum register.
+        :param num_results: The size of the global classical register.
+        """
+        ...
+
+    @property
+    def qubits(self) -> Tuple[Qubit, ...]:
+        """A sequence of qubits representing the global quantum register."""
+        ...
+
+    @property
+    def results(self) -> Tuple[Ref, ...]:
+        """A sequence of references to results representing the global classical register."""
+        ...
+
+    @property
+    def builder(self) -> Builder:
+        """The instruction builder."""
+        ...
+
+    def ir(self) -> str:
+        """Emits the LLVM IR for the module as plain text."""
+        ...
+
+    def bitcode(self) -> bytes:
+        """Emits the LLVM bitcode for the module as a sequence of bytes."""
+        ...
 
 
 class BasicQisBuilder:
diff --git a/pyqir-generator/pyqir/generator/builder.py b/pyqir-generator/pyqir/generator/builder.py
deleted file mode 100644
index e883a08a..00000000
--- a/pyqir-generator/pyqir/generator/builder.py
+++ /dev/null
@@ -1,4 +0,0 @@
-# Copyright (c) Microsoft Corporation.
-# Licensed under the MIT License.
-
-from pyqir.generator._native import Builder as Builder
diff --git a/pyqir-generator/pyqir/generator/builder.pyi b/pyqir-generator/pyqir/generator/builder.pyi
deleted file mode 100644
index 0db4b864..00000000
--- a/pyqir-generator/pyqir/generator/builder.pyi
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (c) Microsoft Corporation.
-# Licensed under the MIT License.
-
-class Builder:
-    """An instruction builder."""
-    ...
diff --git a/pyqir-generator/pyqir/generator/module.py b/pyqir-generator/pyqir/generator/module.py
deleted file mode 100644
index d5234164..00000000
--- a/pyqir-generator/pyqir/generator/module.py
+++ /dev/null
@@ -1,4 +0,0 @@
-# Copyright (c) Microsoft Corporation.
-# Licensed under the MIT License.
-
-from pyqir.generator._native import SimpleModule as SimpleModule
diff --git a/pyqir-generator/pyqir/generator/module.pyi b/pyqir-generator/pyqir/generator/module.pyi
deleted file mode 100644
index 28d97ed6..00000000
--- a/pyqir-generator/pyqir/generator/module.pyi
+++ /dev/null
@@ -1,57 +0,0 @@
-# Copyright (c) Microsoft Corporation.
-# Licensed under the MIT License.
-
-from pyqir.generator.builder import Builder
-from pyqir.generator.value import Qubit, Ref
-from typing import Tuple
-
-
-class SimpleModule:
-    """
-    A simple module represents an executable QIR program with these
-    restrictions:
-
-    - There is one global quantum register and one global classical register.
-      Both are allocated automatically with a fixed size before the program
-      starts.
-    - There is only a single function that runs as the entry point.
-    """
-
-    def __init__(
-        self,
-        name: str,
-        num_qubits: int,
-        num_results: int,
-    ) -> None:
-        """
-        Initializes the module with a name and the number of qubits and results
-        in the quantum and classical registers.
-
-        :param name: The name of the module.
-        :param num_qubits: The size of the global quantum register.
-        :param num_results: The size of the global classical register.
-        """
-        ...
-
-    @property
-    def qubits(self) -> Tuple[Qubit, ...]:
-        """A sequence of qubits representing the global quantum register."""
-        ...
-
-    @property
-    def results(self) -> Tuple[Ref, ...]:
-        """A sequence of references to results representing the global classical register."""
-        ...
-
-    @property
-    def builder(self) -> Builder:
-        """The instruction builder."""
-        ...
-
-    def ir(self) -> str:
-        """Emits the LLVM IR for the module as plain text."""
-        ...
-
-    def bitcode(self) -> bytes:
-        """Emits the LLVM bitcode for the module as a sequence of bytes."""
-        ...
diff --git a/pyqir-generator/pyqir/generator/qis.py b/pyqir-generator/pyqir/generator/qis.py
deleted file mode 100644
index b857822a..00000000
--- a/pyqir-generator/pyqir/generator/qis.py
+++ /dev/null
@@ -1,4 +0,0 @@
-# Copyright (c) Microsoft Corporation.
-# Licensed under the MIT License.
-
-from pyqir.generator._native import BasicQisBuilder as BasicQisBuilder
diff --git a/pyqir-generator/pyqir/generator/value.py b/pyqir-generator/pyqir/generator/value.py
deleted file mode 100644
index 9b93ab7e..00000000
--- a/pyqir-generator/pyqir/generator/value.py
+++ /dev/null
@@ -1,4 +0,0 @@
-# Copyright (c) Microsoft Corporation.
-# Licensed under the MIT License.
-
-from pyqir.generator._native import Qubit as Qubit, Ref as Ref
diff --git a/pyqir-generator/pyqir/generator/value.pyi b/pyqir-generator/pyqir/generator/value.pyi
deleted file mode 100644
index de18698a..00000000
--- a/pyqir-generator/pyqir/generator/value.pyi
+++ /dev/null
@@ -1,11 +0,0 @@
-# Copyright (c) Microsoft Corporation.
-# Licensed under the MIT License.
-
-class Qubit:
-    """A qubit identifier."""
-    ...
-
-
-class Ref:
-    """A reference to an element in memory."""
-    ...
diff --git a/pyqir-jit/pyqir/jit/__init__.py b/pyqir-jit/pyqir/jit/__init__.py
index 6e80a4ad..c3e7fcf2 100644
--- a/pyqir-jit/pyqir/jit/__init__.py
+++ b/pyqir-jit/pyqir/jit/__init__.py
@@ -1,3 +1,6 @@
-# Copyright(c) Microsoft Corporation.
+# Copyright (c) Microsoft Corporation.
 # Licensed under the MIT License.
 
+from pyqir.jit._gatelogger import GateLogger as GateLogger
+from pyqir.jit._gateset import GateSet as GateSet
+from pyqir.jit._nonadaptivejit import NonadaptiveJit as NonadaptiveJit
diff --git a/pyqir-jit/pyqir/jit/gatelogger.py b/pyqir-jit/pyqir/jit/_gatelogger.py
similarity index 96%
rename from pyqir-jit/pyqir/jit/gatelogger.py
rename to pyqir-jit/pyqir/jit/_gatelogger.py
index 62a5f005..26ca00aa 100644
--- a/pyqir-jit/pyqir/jit/gatelogger.py
+++ b/pyqir-jit/pyqir/jit/_gatelogger.py
@@ -1,7 +1,7 @@
-# Copyright(c) Microsoft Corporation.
+# Copyright (c) Microsoft Corporation.
 # Licensed under the MIT License.
 
-from pyqir.jit.gateset import GateSet
+from pyqir.jit._gateset import GateSet
 
 
 class GateLogger(GateSet):
diff --git a/pyqir-jit/pyqir/jit/gateset.py b/pyqir-jit/pyqir/jit/_gateset.py
similarity index 94%
rename from pyqir-jit/pyqir/jit/gateset.py
rename to pyqir-jit/pyqir/jit/_gateset.py
index 6704ffa5..d30bcd83 100644
--- a/pyqir-jit/pyqir/jit/gateset.py
+++ b/pyqir-jit/pyqir/jit/_gateset.py
@@ -1,7 +1,7 @@
-# Copyright(c) Microsoft Corporation.
+# Copyright (c) Microsoft Corporation.
 # Licensed under the MIT License.
 
-class GateSet(object):
+class GateSet:
     """
     Defines the quantum circuit operations which may be registered for
     callbacks during JIT execution of QIR
diff --git a/pyqir-jit/pyqir/jit/nonadaptivejit.py b/pyqir-jit/pyqir/jit/_nonadaptivejit.py
similarity index 95%
rename from pyqir-jit/pyqir/jit/nonadaptivejit.py
rename to pyqir-jit/pyqir/jit/_nonadaptivejit.py
index a683d931..80e4d7e2 100644
--- a/pyqir-jit/pyqir/jit/nonadaptivejit.py
+++ b/pyqir-jit/pyqir/jit/_nonadaptivejit.py
@@ -1,12 +1,12 @@
 # Copyright (c) Microsoft Corporation.
 # Licensed under the MIT License.
 
-from typing import List, Optional
-from pyqir.jit.gateset import GateSet
+from pyqir.jit._gateset import GateSet
 from pyqir.jit._native import PyNonadaptiveJit
+from typing import List, Optional
 
 
-class NonadaptiveJit(object):
+class NonadaptiveJit:
     """
     The non-adaptive JIT evaluates QIR programs without simulating the quantum
     state. Measurement results are pre-determined before the program starts.
diff --git a/pyqir-parser/pyqir/parser/__init__.py b/pyqir-parser/pyqir/parser/__init__.py
index d2b1761f..338b29a9 100644
--- a/pyqir-parser/pyqir/parser/__init__.py
+++ b/pyqir-parser/pyqir/parser/__init__.py
@@ -1,4 +1,4 @@
-# Copyright(c) Microsoft Corporation.
+# Copyright (c) Microsoft Corporation.
 # Licensed under the MIT License.
 
-from pyqir.parser.parser import *
\ No newline at end of file
+from pyqir.parser._parser import *
diff --git a/pyqir-parser/pyqir/parser/parser.py b/pyqir-parser/pyqir/parser/_parser.py
similarity index 92%
rename from pyqir-parser/pyqir/parser/parser.py
rename to pyqir-parser/pyqir/parser/_parser.py
index edf2a6fe..86f2c77d 100644
--- a/pyqir-parser/pyqir/parser/parser.py
+++ b/pyqir-parser/pyqir/parser/_parser.py
@@ -1,9 +1,79 @@
-# Copyright(c) Microsoft Corporation.
+# Copyright (c) Microsoft Corporation.
 # Licensed under the MIT License.
 
-from pyqir.parser._native import *
+from pyqir.parser._native import (
+    PyQirModule,
+    PyQirFunction,
+    PyQirParameter,
+    PyQirBasicBlock,
+    PyQirInstruction,
+    PyQirTerminator,
+    PyQirOperand,
+    PyQirType,
+    module_from_bitcode
+)
 from typing import List, Optional, Tuple
 
+__all__ = [
+    "QirType",
+    "QirVoidType",
+    "QirIntegerType",
+    "QirPointerType",
+    "QirDoubleType",
+    "QirArrayType",
+    "QirStructType",
+    "QirNamedStructType",
+    "QirQubitType",
+    "QirResultType",
+    "QirOperand",
+    "QirLocalOperand",
+    "QirConstant",
+    "QirIntConstant",
+    "QirDoubleConstant",
+    "QirNullConstant",
+    "QirQubitConstant",
+    "QirResultConstant",
+    "QirTerminator",
+    "QirRetTerminator",
+    "QirBrTerminator",
+    "QirCondBrTerminator",
+    "QirSwitchTerminator",
+    "QirUnreachableTerminator",
+    "QirInstr",
+    "QirOpInstr",
+    "QirAddInstr",
+    "QirSubInstr",
+    "QirMulInstr",
+    "QirUDivInstr",
+    "QirSDivInstr",
+    "QirURemInstr",
+    "QirSRemInstr",
+    "QirAndInstr",
+    "QirOrInstr",
+    "QirXorInstr",
+    "QirShlInstr",
+    "QirLShrInstr",
+    "QirAShrInstr",
+    "QirFAddInstr",
+    "QirFSubInstr",
+    "QirFMulInstr",
+    "QirFDivInstr",
+    "QirFRemInstr",
+    "QirFNegInstr",
+    "QirICmpInstr",
+    "QirFCmpInstr",
+    "QirPhiInstr",
+    "QirCallInstr",
+    "QirQisCallInstr",
+    "QirRtCallInstr",
+    "QirQirCallInstr",
+    "QirBlock",
+    "QirParameter",
+    "QirFunction",
+    "QirModule",
+    "module_from_bitcode",
+]
+
 
 class QirType:
     """
@@ -35,12 +105,14 @@ def __new__(cls, ty: PyQirType):
     def __init__(self, ty: PyQirType):
         self.ty = ty
 
+
 class QirVoidType(QirType):
     """
     Instances of QirVoidType represent a void type in QIR.
     """
     pass
 
+
 class QirIntegerType(QirType):
     """
     Instances of QirIntegerType represent a signed integer in QIR. Note that there is no unsigned
@@ -54,6 +126,7 @@ def width(self) -> int:
         """
         return self.ty.integer_width
 
+
 class QirPointerType(QirType):
     """
     Instances of QirPointerType represent a pointer to some other type in QIR.
@@ -75,12 +148,14 @@ def addrspace(self):
         """
         return self.ty.pointer_addrspace
 
+
 class QirDoubleType(QirType):
     """
     Instances of QirDoubleType represent the double-sized floating point type in a QIR program.
     """
     pass
 
+
 class QirArrayType(QirType):
     """
     Instances of the QirArrayType represent the native LLVM fixed-length array type in a QIR program.
@@ -92,7 +167,8 @@ def element_types(self) -> List[QirType]:
         Gets the ordered list of QirTypes representing the underlying array types.
         """
         if not hasattr(self, "_element_types"):
-            self._element_types = [QirType(i) for i in self.ty.array_element_type]
+            self._element_types = [QirType(i)
+                                   for i in self.ty.array_element_type]
         return self._element_types
 
     @property
@@ -102,6 +178,7 @@ def element_count(self) -> int:
         """
         return self.ty.array_num_elements
 
+
 class QirStructType(QirType):
     """
     Instances of QirStructType represent an anonymous struct with inline defined types in QIR.
@@ -113,9 +190,12 @@ def struct_element_types(self) -> List[QirType]:
         Gets the ordered list of QirTypes representing the underlying struct types.
         """
         if not hasattr(self, "_struct_element_types"):
-            self._struct_element_types = [QirType(i) for i in self.ty.struct_element_types]
+            self._struct_element_types = [
+                QirType(i) for i in self.ty.struct_element_types
+            ]
         return self._struct_element_types
 
+
 class QirNamedStructType(QirType):
     """
     Instances of QirNamedStruct represent a globally defined struct, often used to represent opaque
@@ -129,6 +209,7 @@ def name(self) -> str:
         """
         return self.ty.named_struct_name
 
+
 class QirQubitType(QirNamedStructType):
     """
     Instances of QirQubitType are specific QIR opaque pointer corresponding to the Qubit special
@@ -136,6 +217,7 @@ class QirQubitType(QirNamedStructType):
     """
     pass
 
+
 class QirResultType(QirNamedStructType):
     """
     Instances of QirResultType are specific QIR opaque pointer corresponding to the Result special
@@ -143,6 +225,7 @@ class QirResultType(QirNamedStructType):
     """
     pass
 
+
 class QirOperand:
     """
     Instances of QirOperand represent an instance in a QIR program, either a local operand (variable)
@@ -171,6 +254,7 @@ def __init__(self, op: PyQirOperand):
         self.op = op
         self.const = op.constant
 
+
 class QirLocalOperand(QirOperand):
     """
     Instances of QirLocalOperand represent a typed local variable in a QIR program.
@@ -193,6 +277,7 @@ def type(self) -> QirType:
             self._type = QirType(self.op.local_type)
         return self._type
 
+
 class QirConstant(QirOperand):
     """
     Instances of QirConstant represent a constant value in a QIR program.
@@ -207,6 +292,7 @@ def type(self) -> QirType:
             self._type = QirType(self.const.type)
         return self._type
 
+
 class QirIntConstant(QirConstant):
     """
     Instances of QirIntConstant represent a constant integer value in a QIR program.
@@ -218,7 +304,7 @@ def value(self) -> int:
         Gets the integer value for this constant.
         """
         return self.const.int_value
-    
+
     @property
     def width(self) -> int:
         """
@@ -226,6 +312,7 @@ def width(self) -> int:
         """
         return self.const.int_width
 
+
 class QirDoubleConstant(QirConstant):
     """
     Instances of QirDoubleConstant represent a constant double-sized float value in a QIR program.
@@ -238,6 +325,7 @@ def value(self) -> float:
         """
         return self.const.float_double_value
 
+
 class QirNullConstant(QirConstant):
     """
     Instances of QirNullConstant represent a constant null pointer in a QIR program. Use the type
@@ -251,6 +339,7 @@ def value(self):
         """
         return None
 
+
 class QirQubitConstant(QirConstant):
     """
     Instances of QirQubitConstant represent a statically allocated qubit id in a QIR program.
@@ -270,6 +359,7 @@ def id(self) -> int:
         """
         return self.value
 
+
 class QirResultConstant(QirConstant):
     """
     Instances of QirResultConstant represent a statically allocated result id in a QIR program.
@@ -289,12 +379,13 @@ def id(self) -> int:
         """
         return self.value
 
+
 class QirTerminator:
     """
     Instances of QirTerminator represent the special final instruction at the end of a block that
     indicates how control flow should transfer.
     """
-    
+
     def __new__(cls, term: PyQirTerminator):
         if term.is_ret:
             return super().__new__(QirRetTerminator)
@@ -312,6 +403,7 @@ def __new__(cls, term: PyQirTerminator):
     def __init__(self, term: PyQirTerminator) -> None:
         self.term = term
 
+
 class QirRetTerminator(QirTerminator):
     """
     Instances of QirRetTerminator represent the ret instruction in a QIR program.
@@ -326,6 +418,7 @@ def operand(self) -> QirOperand:
             self._operand = QirOperand(self.term.ret_operand)
         return self._operand
 
+
 class QirBrTerminator(QirTerminator):
     """
     Instances of QirBrTerminator represent a branch terminator instruction that unconditionally
@@ -339,6 +432,7 @@ def dest(self) -> str:
         """
         return self.term.br_dest
 
+
 class QirCondBrTerminator(QirTerminator):
     """
     Instances of QirCondBrTerminator represent a conditional branch terminator instruction that
@@ -368,13 +462,14 @@ def false_dest(self) -> str:
         """
         return self.term.condbr_false_dest
 
+
 class QirSwitchTerminator(QirTerminator):
     """
     Instances of QirSwitchTerminator represent a switch terminator instruction that can jump
     to one or more blocks based on matching values of a given operand, or jump to a fallback block
     in the case that no matches are found.
     """
-    
+
     @property
     def operand(self) -> QirLocalOperand:
         """
@@ -391,7 +486,8 @@ def dest_pairs(self) -> List[Tuple[QirConstant, str]]:
         matching block name to jump to if the comparison succeeds.
         """
         if not hasattr(self, "_dest_pairs"):
-            self._dest_pairs = [(QirConstant(p[0]), p[1]) for p in self.term.switch_dests]
+            self._dest_pairs = [(QirConstant(p[0]), p[1])
+                                for p in self.term.switch_dests]
         return self._dest_pairs
 
     @property
@@ -402,6 +498,7 @@ def default_dest(self) -> str:
         """
         return self.term.switch_default_dest
 
+
 class QirUnreachableTerminator(QirTerminator):
     """
     Instances of QirUnreachableTerminator represent an unreachable terminator instruction. As the name
@@ -410,6 +507,7 @@ class QirUnreachableTerminator(QirTerminator):
     """
     pass
 
+
 class QirInstr:
     """
     Instances of QirInstr represent an instruction within a block of a QIR program. See the subclasses
@@ -494,6 +592,7 @@ def type(self) -> QirType:
             self._type = QirType(self.instr.type)
         return self._type
 
+
 class QirOpInstr(QirInstr):
     """
     Instances of QirOpInstr represent the class of instructions that have one or more operands that
@@ -506,123 +605,144 @@ def target_operands(self) -> List[QirOperand]:
         Gets the list of operands that this instruction operates on.
         """
         if not hasattr(self, "_target_operads"):
-            self._target_operads = [QirOperand(i) for i in self.instr.target_operands]
+            self._target_operads = [QirOperand(i)
+                                    for i in self.instr.target_operands]
         return self._target_operads
 
+
 class QirAddInstr(QirOpInstr):
     """
     Instances of QirAddIntr represent an integer addition instruction that takes two operands.
     """
     pass
 
+
 class QirSubInstr(QirOpInstr):
     """
     Instances of QirSubIntr represent an integer subtraction instruction that takes two operands.
     """
     pass
 
+
 class QirMulInstr(QirOpInstr):
     """
     Instances of QirMulIntr represent an integer multiplication instruction that takes two operands.
     """
     pass
 
+
 class QirUDivInstr(QirOpInstr):
     """
     Instances of QirUDivIntr represent an unsigned integer division instruction that takes two operands.
     """
     pass
 
+
 class QirSDivInstr(QirOpInstr):
     """
     Instances of QirSDivIntr represent a signed integer division instruction that takes two operands.
     """
     pass
 
+
 class QirURemInstr(QirOpInstr):
     """
     Instances of QirURemIntr represent an unsigned integer remainder instruction that takes two operands.
     """
     pass
 
+
 class QirSRemInstr(QirOpInstr):
     """
     Instances of QirSRemIntr represent a signed integer remainder instruction that takes two operands.
     """
     pass
 
+
 class QirAndInstr(QirOpInstr):
     """
     Instances of QirAndIntr represent a boolean and instruction that takes two operands.
     """
     pass
 
+
 class QirOrInstr(QirOpInstr):
     """
     Instances of QirOrIntr represent a boolean or instruction that takes two operands.
     """
     pass
 
+
 class QirXorInstr(QirOpInstr):
     """
     Instances of QirXorIntr represent a boolean xor instruction that takes two operands.
     """
     pass
 
+
 class QirShlInstr(QirOpInstr):
     """
     Instances of QirShlIntr represent a bitwise shift left instruction that takes two operands.
     """
     pass
 
+
 class QirLShrInstr(QirOpInstr):
     """
     Instances of QirLShrIntr represent a logical bitwise shift right instruction that takes two operands.
     """
     pass
 
+
 class QirAShrInstr(QirOpInstr):
     """
     Instances of QirLShrIntr represent an arithmetic bitwise shift right instruction that takes two operands.
     """
     pass
 
+
 class QirFAddInstr(QirOpInstr):
     """
     Instances of QirFAddIntr represent a floating-point addition instruction that takes two operands.
     """
     pass
 
+
 class QirFSubInstr(QirOpInstr):
     """
     Instances of QirFSubIntr represent a floating-point subtraction instruction that takes two operands.
     """
     pass
 
+
 class QirFMulInstr(QirOpInstr):
     """
     Instances of QirFMulIntr represent a floating-point multiplication instruction that takes two operands.
     """
     pass
 
+
 class QirFDivInstr(QirOpInstr):
     """
     Instances of QirFDivIntr represent a floating-point division instruction that takes two operands.
     """
     pass
 
+
 class QirFRemInstr(QirOpInstr):
     """
     Instances of QirFRemIntr represent a floating-point remainder instruction that takes two operands.
     """
     pass
 
+
 class QirFNegInstr(QirOpInstr):
     """
     Instances of QirFNegIntr represent a floating-point negation instruction that takes one operand.
     """
     pass
 
+
 class QirICmpInstr(QirOpInstr):
     """
     Instances of QirICmpIntr represent an integer comparison instruction that takes two operands,
@@ -637,6 +757,7 @@ def predicate(self) -> str:
         """
         return self.instr.icmp_predicate
 
+
 class QirFCmpInstr(QirOpInstr):
     """
     Instances of QirFCmpInstr represent a floating-point comparison instruction that takes two operands,
@@ -652,6 +773,7 @@ def predicate(self) -> str:
         """
         return self.instr.fcmp_predicate
 
+
 class QirPhiInstr(QirInstr):
     """
     Instances of QirPhiInstr represent a phi instruction that selects a value for an operand based
@@ -665,7 +787,8 @@ def incoming_values(self) -> List[Tuple[QirOperand, str]]:
         for the value to use and the string name of the originating block.
         """
         if not hasattr(self, "_incoming_values"):
-            self._incoming_values = [(QirOperand(p[0]), p[1]) for p in self.instr.phi_incoming_values]
+            self._incoming_values = [(QirOperand(p[0]), p[1])
+                                     for p in self.instr.phi_incoming_values]
         return self._incoming_values
 
     def get_incoming_value_for_name(self, name: str) -> Optional[QirOperand]:
@@ -680,6 +803,7 @@ def get_incoming_value_for_name(self, name: str) -> Optional[QirOperand]:
         else:
             return None
 
+
 class QirCallInstr(QirInstr):
     """
     Instances of QirCallInstr represent a call instruction in a QIR program.
@@ -698,9 +822,11 @@ def func_args(self) -> List[QirOperand]:
         Gets the list of QirOperand instances that are passed as arguments to the function call.
         """
         if not hasattr(self, "_func_args"):
-            self._func_args = [QirOperand(i) for i in self.instr.call_func_params]
+            self._func_args = [QirOperand(i)
+                               for i in self.instr.call_func_params]
         return self._func_args
 
+
 class QirQisCallInstr(QirCallInstr):
     """
     Instances of QirQisCallInstr represent a call instruction where the function name begins with
@@ -708,6 +834,7 @@ class QirQisCallInstr(QirCallInstr):
     """
     pass
 
+
 class QirRtCallInstr(QirCallInstr):
     """
     Instances of QirRtCallInstr represent a call instruction where the function name begins with
@@ -715,6 +842,7 @@ class QirRtCallInstr(QirCallInstr):
     """
     pass
 
+
 class QirQirCallInstr(QirCallInstr):
     """
     Instances of QirQirCallInstr represent a call instruction where the function name begins with
@@ -722,6 +850,7 @@ class QirQirCallInstr(QirCallInstr):
     """
     pass
 
+
 class QirBlock:
     """
     Instances of the QirBlock type represent a basic block within a function body. Each basic block is
@@ -784,6 +913,7 @@ def get_phi_pairs_by_source_name(self, name: str) -> List[Tuple[str, QirOperand]
         """
         return [(p[0], QirOperand(p[1])) for p in self.block.get_phi_pairs_by_source_name(name)]
 
+
 class QirParameter:
     """
     Instances of the QirParameter type describe a parameter in a function definition or declaration. They
@@ -811,6 +941,7 @@ def type(self) -> QirType:
             self._type = QirType(self.param.type)
         return self._type
 
+
 class QirFunction:
     """
     Instances of the QirFunction type represent a single function in the QIR program. They
@@ -905,6 +1036,7 @@ def get_instruction_by_output_name(self, name: str) -> Optional[QirInstr]:
             return QirInstr(instr)
         return None
 
+
 class QirModule:
     """
     Instances of QirModule parse a QIR program from bitcode into an in-memory
@@ -918,7 +1050,8 @@ def __init__(self, *args):
         elif isinstance(args[0], str):
             self.module = module_from_bitcode(args[0])
         else:
-            raise TypeError("Unrecognized argument type. Input must be string path to bitcode or PyQirModule object.")
+            raise TypeError(
+                "Unrecognized argument type. Input must be string path to bitcode or PyQirModule object.")
         self._functions = None
         self._interop_funcs = None
         self._entrypoint_funcs = None
@@ -932,7 +1065,6 @@ def functions(self) -> List[QirFunction]:
             self._functions = [QirFunction(i) for i in self.module.functions]
         return self._functions
 
-
     def get_func_by_name(self, name: str) -> Optional[QirFunction]:
         """
         Gets the function with the given name, or None if no matching function is found.
@@ -957,7 +1089,9 @@ def entrypoint_funcs(self) -> List[QirFunction]:
         Gets any functions with the "EntryPoint" attribute.
         """
         if self._entrypoint_funcs == None:
-            self._entrypoint_funcs = [QirFunction(i) for i in self.module.get_entrypoint_funcs()]
+            self._entrypoint_funcs = [
+                QirFunction(i) for i in self.module.get_entrypoint_funcs()
+            ]
         return self._entrypoint_funcs
 
     @property
@@ -966,5 +1100,7 @@ def interop_funcs(self) -> List[QirFunction]:
         Gets any functions with the "InteropFriendly" attribute.
         """
         if self._interop_funcs == None:
-            self._interop_funcs = [QirFunction(i) for i in self.module.get_interop_funcs()]
+            self._interop_funcs = [
+                QirFunction(i) for i in self.module.get_interop_funcs()
+            ]
         return self._interop_funcs


Test patch:
diff --git a/pyqir-generator/tests/test_api.py b/pyqir-generator/tests/test_api.py
index bf39b886..f776a8de 100644
--- a/pyqir-generator/tests/test_api.py
+++ b/pyqir-generator/tests/test_api.py
@@ -7,8 +7,7 @@
 about the generated IR.
 """
 
-from pyqir.generator.module import SimpleModule
-from pyqir.generator.qis import BasicQisBuilder
+from pyqir.generator import BasicQisBuilder, SimpleModule
 
 
 def test_bell() -> None:
diff --git a/pyqir-jit/tests/test_api.py b/pyqir-jit/tests/test_api.py
index 39f2286b..c6f2f6d5 100644
--- a/pyqir-jit/tests/test_api.py
+++ b/pyqir-jit/tests/test_api.py
@@ -1,8 +1,7 @@
 # Copyright (c) Microsoft Corporation.
 # Licensed under the MIT License.
 
-from pyqir.jit.nonadaptivejit import NonadaptiveJit
-from pyqir.jit.gatelogger import GateLogger
+from pyqir.jit import GateLogger, NonadaptiveJit
 
 
 def test_bell_qir():
diff --git a/pyqir-parser/tests/test_api.py b/pyqir-parser/tests/test_api.py
index bf248e25..850ffa3f 100644
--- a/pyqir-parser/tests/test_api.py
+++ b/pyqir-parser/tests/test_api.py
@@ -1,94 +1,98 @@
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+
 from pyqir.parser import *
-import pytest
+
 
 def test_parser_pythonic():
     mod = QirModule("tests/teleportchain.baseprofile.bc")
     func_name = "TeleportChain__DemonstrateTeleportationUsingPresharedEntanglement__Interop"
     func = mod.get_func_by_name(func_name)
-    assert(func.name == func_name)
+    assert func.name == func_name
     func_list = mod.functions
-    assert(len(func_list) == 1)
-    assert(func_list[0].name == func_name)
-    assert(hash(func_list[0]) == hash(mod.functions[0]))
+    assert len(func_list) == 1
+    assert func_list[0].name == func_name
+    assert hash(func_list[0]) == hash(mod.functions[0])
     interop_funcs = mod.get_funcs_by_attr("InteropFriendly")
-    assert(len(interop_funcs) == 1)
-    assert(len(mod.interop_funcs) == 1)
-    assert(mod.interop_funcs[0].name == interop_funcs[0].name)
-    assert(len(mod.entrypoint_funcs) == 0)
+    assert len(interop_funcs) == 1
+    assert len(mod.interop_funcs) == 1
+    assert mod.interop_funcs[0].name == interop_funcs[0].name
+    assert len(mod.entrypoint_funcs) == 0
     blocks = func.blocks
-    assert(len(blocks) == 9)
-    assert(hash(blocks[0]) == hash(func.blocks[0]))
-    assert(hash(blocks[0].instructions[0]) == hash(func.blocks[0].instructions[0]))
-    assert(blocks[0].name == "entry")
+    assert len(blocks) == 9
+    assert hash(blocks[0]) == hash(func.blocks[0])
+    assert (hash(blocks[0].instructions[0])
+            == hash(func.blocks[0].instructions[0]))
+    assert blocks[0].name == "entry"
     term = blocks[0].terminator
-    assert(isinstance(term, QirTerminator))
-    assert(isinstance(term, QirCondBrTerminator))
-    assert(term.true_dest == "then0__1.i.i.i")
-    assert(term.false_dest == "continue__1.i.i.i")
-    assert(term.condition.name == "0")
-    assert(blocks[1].terminator.dest == "continue__1.i.i.i")
-    assert(blocks[8].terminator.operand.type.width == 8)
+    assert isinstance(term, QirTerminator)
+    assert isinstance(term, QirCondBrTerminator)
+    assert term.true_dest == "then0__1.i.i.i"
+    assert term.false_dest == "continue__1.i.i.i"
+    assert term.condition.name == "0"
+    assert blocks[1].terminator.dest == "continue__1.i.i.i"
+    assert blocks[8].terminator.operand.type.width == 8
     block = func.get_block_by_name("then0__2.i.i3.i")
-    assert(isinstance(block.instructions[0], QirQisCallInstr))
-    assert(isinstance(block.instructions[0].func_args[0], QirQubitConstant))
-    assert(block.instructions[0].func_args[0].id == 5)
+    assert isinstance(block.instructions[0], QirQisCallInstr)
+    assert isinstance(block.instructions[0].func_args[0], QirQubitConstant)
+    assert block.instructions[0].func_args[0].id == 5
     block = func.get_block_by_name("continue__1.i.i2.i")
     var_name = block.terminator.condition.name
     instr = func.get_instruction_by_output_name(var_name)
-    assert(isinstance(instr, QirQirCallInstr))
-    assert(instr.output_name == var_name)
-    assert(instr.func_name == "__quantum__qir__read_result")
-    assert(instr.func_args[0].id == 3)
-    assert(isinstance(instr.type, QirIntegerType))
-    assert(instr.type.width == 1)
+    assert isinstance(instr, QirQirCallInstr)
+    assert instr.output_name == var_name
+    assert instr.func_name == "__quantum__qir__read_result"
+    assert instr.func_args[0].id == 3
+    assert isinstance(instr.type, QirIntegerType)
+    assert instr.type.width == 1
 
 
 def test_parser():
     mod = module_from_bitcode("tests/teleportchain.baseprofile.bc")
     func_name = "TeleportChain__DemonstrateTeleportationUsingPresharedEntanglement__Interop"
     func = mod.get_func_by_name(func_name)
-    assert(func.name == func_name)
-    assert(len(func.parameters) == 0)
-    assert(func.return_type.is_integer)
+    assert func.name == func_name
+    assert len(func.parameters) == 0
+    assert func.return_type.is_integer
     func_list = mod.functions
-    assert(len(func_list) == 1)
-    assert(func_list[0].name == func_name)
+    assert len(func_list) == 1
+    assert func_list[0].name == func_name
     interop_funcs = mod.get_funcs_by_attr("InteropFriendly")
-    assert(len(interop_funcs) == 1)
-    assert(interop_funcs[0].name == func_name)
-    assert(interop_funcs[0].get_attribute_value("requiredQubits") == "6")
-    assert(interop_funcs[0].required_qubits == 6)
+    assert len(interop_funcs) == 1
+    assert interop_funcs[0].name == func_name
+    assert interop_funcs[0].get_attribute_value("requiredQubits") == "6"
+    assert interop_funcs[0].required_qubits == 6
     blocks = func.blocks
-    assert(len(blocks) == 9)
-    assert(blocks[0].name == "entry")
+    assert len(blocks) == 9
+    assert blocks[0].name == "entry"
     entry_block = func.get_block_by_name("entry")
-    assert(entry_block.name == "entry")
-    assert(entry_block.terminator.is_condbr)
-    assert(not entry_block.terminator.is_ret)
-    assert(entry_block.terminator.condbr_true_dest == "then0__1.i.i.i")
-    assert(entry_block.terminator.condbr_false_dest == "continue__1.i.i.i")
-    assert(blocks[1].terminator.is_br)
-    assert(blocks[1].terminator.br_dest == "continue__1.i.i.i")
-    assert(blocks[8].terminator.is_ret)
-    assert(len(entry_block.instructions) == 11)
-    assert(entry_block.instructions[0].is_call)
-    assert(entry_block.instructions[0].call_func_name == "__quantum__qis__h__body")
-    assert(entry_block.instructions[0].is_qis_call)
+    assert entry_block.name == "entry"
+    assert entry_block.terminator.is_condbr
+    assert not entry_block.terminator.is_ret
+    assert entry_block.terminator.condbr_true_dest == "then0__1.i.i.i"
+    assert entry_block.terminator.condbr_false_dest == "continue__1.i.i.i"
+    assert blocks[1].terminator.is_br
+    assert blocks[1].terminator.br_dest == "continue__1.i.i.i"
+    assert blocks[8].terminator.is_ret
+    assert len(entry_block.instructions) == 11
+    assert entry_block.instructions[0].is_call
+    assert entry_block.instructions[0].call_func_name == "__quantum__qis__h__body"
+    assert entry_block.instructions[0].is_qis_call
     param_list = entry_block.instructions[0].call_func_params
-    assert(len(param_list) == 1)
-    assert(param_list[0].is_constant)
-    assert(param_list[0].constant.is_qubit)
-    assert(param_list[0].constant.qubit_static_id == 0)
-    assert(entry_block.instructions[8].is_qis_call)
-    assert(entry_block.instructions[8].call_func_name == "__quantum__qis__mz__body")
-    assert(entry_block.instructions[8].call_func_params[0].constant.qubit_static_id == 1)
-    assert(entry_block.instructions[8].call_func_params[1].constant.result_static_id == 0)
+    assert len(param_list) == 1
+    assert param_list[0].is_constant
+    assert param_list[0].constant.is_qubit
+    assert param_list[0].constant.qubit_static_id == 0
+    assert entry_block.instructions[8].is_qis_call
+    assert entry_block.instructions[8].call_func_name == "__quantum__qis__mz__body"
+    assert entry_block.instructions[8].call_func_params[0].constant.qubit_static_id == 1
+    assert entry_block.instructions[8].call_func_params[1].constant.result_static_id == 0
     branch_cond = entry_block.terminator.condbr_condition
-    assert(branch_cond.local_name == "0")
-    assert(entry_block.instructions[10].is_qir_call)
-    assert(entry_block.instructions[10].call_func_name == "__quantum__qir__read_result")
-    assert(entry_block.instructions[10].call_func_params[0].constant.result_static_id == 0)
-    assert(entry_block.instructions[10].has_output)
-    assert(entry_block.instructions[10].output_name == "0")
+    assert branch_cond.local_name == "0"
+    assert entry_block.instructions[10].is_qir_call
+    assert entry_block.instructions[10].call_func_name == "__quantum__qir__read_result"
+    assert entry_block.instructions[10].call_func_params[0].constant.result_static_id == 0
+    assert entry_block.instructions[10].has_output
+    assert entry_block.instructions[10].output_name == "0"
     source_instr = func.get_instruction_by_output_name(branch_cond.local_name)
-    assert(source_instr.call_func_params[0].constant.result_static_id == 0)
+    assert source_instr.call_func_params[0].constant.result_static_id == 0
diff --git a/pyqir-tests/tests/test_if.py b/pyqir-tests/tests/test_if.py
index 1bbcbf61..8f8da7f8 100644
--- a/pyqir-tests/tests/test_if.py
+++ b/pyqir-tests/tests/test_if.py
@@ -1,10 +1,10 @@
-from typing import List, Optional
-from pyqir.generator.module import SimpleModule
-from pyqir.generator.qis import BasicQisBuilder
-from pyqir.jit.nonadaptivejit import NonadaptiveJit
-from pyqir.jit.gatelogger import GateLogger
-from pyqir.jit.gateset import GateSet
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+
+from pyqir.generator import BasicQisBuilder, SimpleModule
+from pyqir.jit import GateLogger, GateSet, NonadaptiveJit
 import tempfile
+from typing import List, Optional
 import unittest
 
 
diff --git a/pyqir-tests/tests/test_teleport.py b/pyqir-tests/tests/test_teleport.py
index c2e94d3a..9ed57853 100644
--- a/pyqir-tests/tests/test_teleport.py
+++ b/pyqir-tests/tests/test_teleport.py
@@ -1,11 +1,10 @@
-from typing import List, Optional
-from pyqir.generator.module import SimpleModule
-from pyqir.generator.qis import BasicQisBuilder
-from pyqir.generator.value import Qubit, Ref
-from pyqir.jit.nonadaptivejit import NonadaptiveJit
-from pyqir.jit.gatelogger import GateLogger
-from pyqir.jit.gateset import GateSet
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+
+from pyqir.generator import BasicQisBuilder, Qubit, Ref, SimpleModule
+from pyqir.jit import GateLogger, GateSet, NonadaptiveJit
 import tempfile
+from typing import List, Optional
 import unittest