# Package HowTo This is a brief introduction to python packages. How to make them, how to install them, how to upload them, how to maintain them, how to work with them. For details consult https://packaging.python.org and other sites. ## Why packages? When working on your project, you typically end up with some scripts, functions and classes that are of more general interest. As a first step, you make some modules, i.e. separate python files, where you collect this code. These modules can be easily imported from other scripts in the very same directory. For example, consider a module `addition.py` and a script `analyze.py` both in the same directory: ```txt ├── addition.py └── analyze.py ``` In `addition.py` we define a function `add_two()`: ``` def add_two(x): return x + 2 ``` We can use this function in `analyze.py` by importing it from `addition.py`: ``` from addition import add_two x = 5 y = add_two(x) ``` This works, however, only if scripts and modules are in the same directory (or if modules are in sub-directories). To make modules available in other places of your file system or even for other people, you need to turn the modules into packages. ## Minimal package First we make a project directory for our new package. Usually the name of the project directory is the same as the one of the package. Here, however, we call the proejct directory `packagehowto` and the name of the package `numerix`. A package is a directory, and the name of the package is the name of this directory, here `numerix`. Inside the package directory we put all the modules, for now just `addition.py`. What makes this directory a package is the presence of a file named `__init__.py`. This file is executed when the package is imported. For now we leave it empty. The package directory resides in the `src/` directory of the project: ```txt packagehowto/ ├── pyproject.toml └── src/ └── numerix ├── __init__.py └── addition.py ``` A package also needs a `pyproject.toml` file. This file contains some metadata about the package and informations about how to build a package. As the bare minimum the content of the `pyproject.toml` file specifies `setuptools` to be used for building the project: ```txt [build-system] requires = ["setuptools"] build-backend = "setuptools.build_meta" ``` With this `pyproject.toml` file, and `addition.py` and `__init__.py` in the `src/numerix` directory, you can install the `numerix` package on your machine and import it from wherever you want. To install the project, go to the project root, here `packagehowto/` and run from your shell ```sh pip3 install . ``` This installs the package of the current project folder `.` somewhere in your home directory. From anywhere in your home file system you now can import this package. The import line of our `analyze.py` file needs to look like this: ``` from numerix.addition import add_two ``` From the addition module of the numerix package the function add_two is imported. You would need to reinstall the package whenever you change your package, for exmple when you add a new function or when you just fix some package code. This is tedious, and that is why there is a `-e` option for `pip install`. So install your package with ```sh pip3 install -e . ``` Then all future changes on your package are immediately available without the need to reinstall the package again. ## The `__init__.py` file This file is your package. In fact, you could write a package that only has an `__init__.py` file in the package directory. All code you want to make available in you package could be place in the `__init__.py` file. For example, if you define a `add_four()` function in `__init__.py` like this: ``` def add_four(x): return x + 4 ``` then it can be imported directly from the package: ``` from numerix import add_four ``` Alternatively, you can define all your functions in module files, like we did with the function `add_two()` in the `addition.py` module. In the `__init__.py` file you can import this function and this way make it available directly from the package. With this line in `__init__.py` ``` from .addition import add_two ``` (note the `.` in front of `addition`!) you can import `add_two()` without specifying the module: ``` from numerix import add_two ``` ## Multiple modules within a package You can have as many modules as you need in your package. Let's add a module `numbers.py` to the `numerix` package ```txt └── numerix ├── __init__.py ├── addition.py └── numbers.py ``` with the following content: ``` from .addition import add_two def three(): return add_two(1) ``` This makes a function `three()` available that can be imported via ``` from numerix.numbers import three ``` Note, that the `numbers.py` module imports a function from the `addition.py` module via a relative import with the `.` in front of `addition`. ## Distribute your package Full pyproject.toml file... README.md LICENSE pypi.org ```sh python3 -m build ``` ```sh python3 -m twine upload dist/* ``` token ## Package version ## Unit tests pytest coverage ```sh pytest -v --cov-report html --cov numerix tests/ ``` ## Documentation