;
}
%exception
{
[...]
}
%{
#define SWIG_FILE_WITH_INIT
#include "myclass.h"
%}
%include "armanpy.i"
%include "myclass.h"
```
]
]
---
class: top
# Module `venv` - Package example
.vspace[]
.vspace[]
.vspace[]
## .hcenter[`C++` + `Armadillo` + `swig` + `setuptools` = Python module]
.vspace[]
.vspace[]
.vspace[]
.vspace[]
.vspace[]
.row.w100[
.column.w30[
.tree[
armanpy_python3
* include
* armanpy
* armanpy.i
* armanpy.hpp
* armanpy_1d.i
* armanpy_2d.i
* armanpy_3d.i
* numpy.i
* myclass.cpp
* myclass.h
* setup.py
* testmod3.i
* **test.py** :arrow_left:
]
]
.column.w70.middle[
## .hcenter[`test.py`]
```python
#!/usr/bin/env python3
import testmod3
import numpy as np
numpyArray = np.array([[0.1, 0.2], [0.3, 0.4]], dtype = np.float64, order = 'F')
print("Initial object:")
print(numpyArray)
mc = testmod3.Myclass(numpyArray)
print("Object in the C++ class (constructor with argument):")
print(mc.myMat)
mc = testmod3.Myclass()
print("Object in the C++ class (empty constructor):")
print(mc.myMat)
a = mc.getEmptyArma(3)
a[0,1] = 1.0
print("Object returned by the C++ method getEmptyArma() and modified:")
print(a)
print("Object returned by the C++ method testArma() (transposition):")
print(mc.testArma(a))
```
]
]
---
# Module `venv` - Package example
## .hcenter[Build and install the package in a `venv` with `setuptools`]
.hcenter[
]
:arrow_right: Installed packages are stored inside the `venv` directory.
:warning: This direct invocation of `setup.py` is **deprecated**. One must use **`pip`** instead.
---
# Module `venv` - Package example
## .hcenter[Build and install the package in a `venv` with `pip`]
.hcenter[
]
:arrow_right: "`pip` is the **prefered** installer program."
:arrow_right: `pip` should be installed by default on a standard Python installation.
---
# Module `pip` - ???
.hcenter.shadow.w70[]
## .hcenter[What was this "`pip install`" thing ?]
---
# Module `pip` - Install packages
.hcenter[:arrow_right: `pip` is a package management system, dedicated to Python packages.]
## :arrow_right: `pip` can build and install **local packages**
[dubrayn@dell5290(myVenv):armanpy_python3] pip install .
Processing /tmp/armanpy_python3
Preparing metadata (setup.py) ... done
Using legacy 'setup.py install' for package-test, since package 'wheel' is not installed.
Installing collected packages: package-test
Running setup.py install for package-test ... done
Successfully installed package-test-1.0
## :arrow_right: `pip` can fetch, [build] and install **remote packages**
[dubrayn@dell5290(myVenv):armanpy_python3] pip install numpy
Collecting numpy
Using cached numpy-1.24.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (17.3 MB)
Installing collected packages: numpy
Successfully installed numpy-1.24.3
[dubrayn@dell5290(myVenv):armanpy_python3] pip install xkcd==2.4.0
Collecting xkcd==2.4.0
Downloading xkcd-2.4.0.zip (14 kB)
Preparing metadata (setup.py) ... done
Using legacy 'setup.py install' for xkcd, since package 'wheel' is not installed.
Installing collected packages: xkcd
Running setup.py install for xkcd ... done
Successfully installed xkcd-2.4.0
:arrow_right: Use "`pip install package==x.y.z`" to install a **specific version** of a package.
:arrow_right: You can use "`python3 -m pip command`" instead of "`pip command`".
---
# Module `pip` - Manage packages
## :arrow_right: `pip` can **list** installed packages in the current `venv`
[dubrayn@dell5290(myVenv):armanpy_python3] pip list
Package Version
------------ -------
numpy 1.24.3
package-test 1.0
pip 22.0.2
setuptools 59.6.0
xkcd 2.4.0
## :arrow_right: `pip` can **uninstall** packages in the current `venv`
[dubrayn@dell5290(myVenv):armanpy_python3] pip uninstall package-test
Found existing installation: package-test 1.0
Uninstalling package-test-1.0:
Would remove:
/tmp/armanpy_python3/myVenv/lib/python3.10/site-packages/_testmod3.cpython-310-x86_64-linux-gnu.so
/tmp/armanpy_python3/myVenv/lib/python3.10/site-packages/package_test-1.0.egg-info
/tmp/armanpy_python3/myVenv/lib/python3.10/site-packages/testmod3.py
Proceed (Y/n)?
Successfully uninstalled package-test-1.0
[dubrayn@dell5290(myVenv):armanpy_python3] pip list
Package Version
---------- -------
numpy 1.24.3
pip 22.0.2
setuptools 59.6.0
xkcd 2.4.0
---
# Module `pip` - Manage packages
## :arrow_right: `pip` can **update** installed packages in the current `venv`
[dubrayn@dell5290(myVenv):armanpy_python3] pip install xkcd -U
Requirement already satisfied: xkcd in ./myVenv/lib/python3.10/site-packages (2.4.0)
Collecting xkcd
Using cached xkcd-2.4.2-py3-none-any.whl
Installing collected packages: xkcd
Attempting uninstall: xkcd
Found existing installation: xkcd 2.4.0
Uninstalling xkcd-2.4.0:
Successfully uninstalled xkcd-2.4.0
Successfully installed xkcd-2.4.2
## :arrow_right: `pip` can **list versions** of a given package
[dubrayn@dell5290(myVenv):armanpy_python3] pip install xkcd==
ERROR: Could not find a version that satisfies the requirement xkcd== (from versions: 2.0, 2.1, 2.2, 2.3, 2.3.1, 2.3.2, 2.3.3, 2.4.0, 2.4.1, 2.4.2)
ERROR: No matching distribution found for xkcd==
## :arrow_right: `pip` cannot **search** for available packages anymore :cry:
[dubrayn@dell5290(myVenv):armanpy_python3] pip search xkcd
ERROR: XMLRPC request failed [code: -32500]
RuntimeError: PyPI no longer supports 'pip search' (or XML-RPC search). Please use https://pypi.org/search (via a browser) instead. See https://warehouse.pypa.io/api-reference/xml-rpc.html#deprecated-methods for more information.
:arrow_right: As stated, go to `https://pypi.org/search` to search for packages.
---
# Module `pip` - "remote packages" ?
.hcenter.shadow.w70[]
## .hcenter[Where do the remote packages come from ?]
---
# `PyPi` - The Python Package Index
.hcenter.w40[]
:arrow_right: "PyPi helps you find and install software **developed and shared** by the Python community."
:arrow_right: `https://pypi.org`
---
# `PyPi` - "by the Python community" ?
.hcenter.w70[]
## .hcenter[So I can share my Python packages ?]
---
class: top
# `PyPi` - Creating a compliant package
.vspace[]
.vspace[]
.vspace[]
## .hcenter[Pure Python module]
.vspace[]
.vspace[]
.vspace[]
.vspace[]
.vspace[]
.row.w100[
.column.w30.middle[
.tree[
pyexample
* LICENSE
* pyexample
* `__`init`__`.py
* module_test_0.py
* module_test_1.py
* module_test_2.py
* README.rst
* **setup.py** :arrow_left:
]
]
.column.w70.middle[
## .hcenter[`setup.py`]
```python
from setuptools import setup
setup(
name='pyexample',
version='0.1.0',
description='A example Python package',
url='https://github.com/shuds13/pyexample',
author='Stephen Hudson',
author_email='shudson@anl.gov',
license='BSD 2-clause',
packages=['pyexample'],
install_requires=['mpi4py>=2.0',
'numpy',
],
classifiers=[
'Development Status :: 1 - Planning',
'Intended Audience :: Science/Research',
'License :: OSI Approved :: BSD License',
'Operating System :: POSIX :: Linux',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
],
)
```
]
]
:arrow_right: Example inspired from [https://github.com/shuds13/pyexample](https://github.com/shuds13/pyexample)
---
class: top
# `PyPi` - Creating a compliant package
.vspace[]
.vspace[]
.vspace[]
## .hcenter[Pure Python module]
.vspace[]
.vspace[]
.vspace[]
.vspace[]
.vspace[]
.row.w100[
.column.w30.middle[
.tree[
pyexample
* LICENSE
* pyexample
* **`__`init`__`.py** :arrow_left:
* module_test_0.py
* module_test_1.py
* module_test_2.py
* README.rst
* setup.py
]
]
.column.w70.middle[
## .hcenter[`pyexample/__init__.py`]
```python
"""
pyexample.
An example python library.
"""
__version__ = "0.1.0"
__author__ = 'Stephen Hudson'
__credits__ = 'Argonne National Laboratory'
```
]
]
.hcenter[:warning: Two version numbers...]
## Create a source distribution
```bash
$ python setup.py sdist
```
:arrow_right: This creates a `pyexample-0.1.0.tar.gz` file that can be distributed.
:arrow_right: To customize the content of the archive, you can use a `MANIFEST.in` file (cf. next slides).
:arrow_right: To send the file to `PyPi`, use the `twine` module (cf. next slides).
---
# `PyPi` - Create a compliant package
.hcenter.shadow.w70[]
## .hcenter[I want to use `C++`, `Armadillo` and `swig` in my Python module !]
---
# `PyPi` - Create a compliant binary package
## Binary packages
* They are called **`wheels`** (the `egg` format is obsolete now).
* They (obviously) depend on the architecture and the environment.
* They speed up the distribution process by avoiding any recompilation.
:arrow_right: Let's build and distribute a `wheel` file then ?
:warning: `PyPi` forbids the upload of generic linux binary wheels.
:arrow_right: Only a "standard" `wheel` binary format is accepted by `PyPi`: **`manylinux`**
## The `manylinux` project
* [https://github.com/pypa/manylinux](https://github.com/pypa/manylinux)
* Goal: "to provide a convenient way to distribute binary Python extensions as wheels on Linux".
* They provide a small set of **platform tags**. Among them, PEP 600 is supposed to be **future-proof**.
* PEP 600: a wheel tagged `manylinux_x_y` shall work on any distro based on `glibc==x.y`.
* Such `manylinux_x_y` tagged wheels should work with `pip>=20.3` and `CPython>=3.8.10`, `CPython>=3.9.5` and `CPython>=3.10.0`.
:arrow_right: To generate `manylinux_x_y` compatible wheels, **Docker images** are available with different toolchains and architectures.
---
# `manylinux` - Build and upload to `PyPi`
## .hcenter[`C++` + `Armadillo` + `swig` + `setuptools` = Python module]
.vspace[]
.vspace[]
.vspace[]
:arrow_right: The files are in **`examples/armanpy_pip`**.
:arrow_right: We are using the `AlmaLinux 8` based Docker image `quay.io/pypa/manylinux_2_28_x86_64`.
:arrow_right: For testing, the package is uploaded to `test.pypi.org` instead of `pypi.org`.
:arrow_right: Create an account on [https://test.pypi.org/account/register](https://test.pypi.org/account/register) to upload.
## Generating and uploading the package
```bash
$ docker run -it --volume $(pwd):/root/ quay.io/pypa/manylinux_2_28_x86_64
[docker] cd root/
[docker] yum -y install epel-release
[docker] yum -y update
[docker] yum -y install swig armadillo-devel boost-devel
[docker] /opt/python/cp310-cp310/bin/pip3.10 install wheel setuptools numpy twine
[docker] /opt/python/cp310-cp310/bin/pip3.10 wheel . --no-deps -w output
[docker] auditwheel repair output/armanpypsa-*-linux_x86_64.whl -w output
[docker] twine upload output/armanpypsa-*-manylinux_2_28_x86_64.whl -r testpypi
```
## Installing the package from `test.pypi.org`
```bash
$ python3 -m venv venv
$ source venv/bin/activate
$ pip3 install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ armanpypsa
$ test_package/test.py
```
:arrow_right: The dependencies (`numpy`) are installed from `pypi.org`.
---
class: top
# `manylinux` - Some explanations
.vspace[]
.vspace[]
.vspace[]
.row.w100[
.column.w30[
.tree[
armanpy_pip/
* armanpy/
* [...]
* armanpypsa.i
* go.sh
* LICENSE
* MANIFEST.in
* **pyproject.toml** :arrow_left:
* README.md
* setup.py
* src/
* myclass.cpp
* myclass.h
* test_package/
* test.py
]
]
.column.w70.middle[
## .hcenter[`pyproject.toml`]
```toml
[build-system]
requires = ["setuptools>=59.6.0", "wheel", "numpy"]
build-backend = "setuptools.build_meta"
[project]
name = "armanpypsa"
version = "0.0.11"
authors = [
{ name="Dubray N.", email="noel.dubray@gmail.com" },
]
description = "Test package for teaching purposes"
readme = "README.md"
requires-python = ">=3.7"
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]
dependencies = ["numpy"]
[project.urls]
"Homepage" = "https://github.com/dubrayn/dubrayn.github.io"
"Bug Tracker" = "https://github.com/dubrayn/dubrayn.github.io/issues"
```
:arrow_right: Notice the different dependencies:
* `build-system/requires` (build)
* `project/dependencies` (runtime)
:warning: `project/version` must not exist in the remote package index.
]
]
---
class: top
# `manylinux` - Some explanations
.vspace[]
.vspace[]
.vspace[]
.row.w100[
.column.w30[
.tree[
armanpy_pip/
* armanpy/
* [...]
* armanpypsa.i
* go.sh
* LICENSE
* **MANIFEST.in** :arrow_left:
* pyproject.toml
* README.md
* setup.py
* src/
* myclass.cpp
* myclass.h
* test_package/
* test.py
]
]
.column.w70.middle[
## .hcenter[`MANIFEST.in`]
```manifest
include LICENSE
include README.md
recursive-include src *.cpp
recursive-include src *.h
recursive-include armanpy *.hpp
recursive-include armanpy *.i
```
:arrow_right: List the files to be included in the `venv` for the build process.
:arrow_right: Wildcards are accepted.
]
]
---
class: top
# `manylinux` - Some explanations
.vspace[]
.vspace[]
.vspace[]
.row.w100[
.column.w30[
.tree[
armanpy_pip/
* armanpy/
* [...]
* armanpypsa.i
* go.sh
* LICENSE
* MANIFEST.in
* pyproject.toml
* README.md
* requirements.txt
* **setup.py** :arrow_left:
* src/
* myclass.cpp
* myclass.h
* test_package/
* test.py
]
]
.column.w70.middle[
## .hcenter[`setup.py`]
```python
from setuptools import setup, Extension
# fix an annoying bug with setuptools
# https://stackoverflow.com/questions/29477298/setup-py-run-build-ext-before-anything-else/48942866#48942866
from setuptools.command.build_py import build_py as _build_py
class build_py(_build_py):
def run(self):
self.run_command("build_ext")
return super().run()
module1 = Extension('_armanpypsa',
include_dirs = ['./armanpy/', 'src/', '/opt/python/cp310-cp310/lib/python3.10/site-packages/numpy/core/include/'],
libraries = ['m', 'z', 'armadillo'],
sources = ['armanpypsa.i', 'src/myclass.cpp'],
swig_opts = ["-c++", "-Wall", "-I.", "-I./armanpy/", "-Isrc/"])
setup (name = 'armanpypsa',
py_modules = ['armanpypsa'],
version = '1.0',
description = 'This is a test package',
options = {"build_ext": {"inplace": False}},
cmdclass = {'build_py' : build_py}, # fix for the bug
ext_modules = [module1])
```
]
]
:arrow_right: Some tweaks to get it to work:
* workaround for a bug in `setuptools` (order of `build` and `build_ext`),
* hardcoded include path for `numpy` (not sure how to do it better, feel free to PR).
---
class: top
# `manylinux` - Some explanations
.vspace[]
.vspace[]
.vspace[]
.row.w100[
.column.w30[
.tree[
armanpy_pip/
* armanpy/
* [...]
* armanpypsa.i
* **go.sh** :arrow_left:
* LICENSE
* MANIFEST.in
* pyproject.toml
* README.md
* requirements.txt
* setup.py
* src/
* myclass.cpp
* myclass.h
* test_package/
* test.py
]
]
.column.w70.middle[
## .hcenter[`go.sh` (launched in the container)]
```bash
#!/bin/bash
yum -y install epel-release
yum -y update
yum -y install swig armadillo-devel boost-devel
/opt/python/cp310-cp310/bin/pip3.10 install wheel setuptools numpy twine
/opt/python/cp310-cp310/bin/pip3.10 wheel . --no-deps -w output
auditwheel repair output/armanpypsa-*-linux_x86_64.whl -w output
/opt/python/cp310-cp310/bin/twine upload output/armanpypsa-*-manylinux_2_28_x86_64.whl -r testpypi
```
:arrow_right: `armadillo-devel` and its deps. are installed from the `epel` repo,
:arrow_right: The chosen Python version is `3.10`.
:arrow_right: The build dependencies are manually installed.
:arrow_right: `auditwheel` creates the final `manylinux_x_y` wheel.
:arrow_right: `twine` is used to upload the wheel directly from the container.
]
]
---
# `manylinux` - Screencast
.hcenter[
]
.hcenter[
:warning: This is a **long** screencast (>7 min), even if the Docker image was already present.
]
---
# Conclusions
## Virtual environments are powerful
:arrow_right: They allow to develop different Python projects on the same computer.
:arrow_right: They allow to decouple and isolate the different Python installs and their associated packages.
## Virtual environments are easy to use and to manage
:arrow_right: Two standard modules to remember: `venv` and `pip`
:arrow_right: Several intuitive commands to remember
:arrow_right: `python -m venv --help`
:arrow_right: `python -m pip --help`
## Virtual environments are used by other tools
:arrow_right: deployment testing with tox ([https://tox.wiki](https://tox.wiki))
:arrow_right: build process (`build`, `pip`...)
:arrow_right: CI / CD
## `PyPi` is a blessing
:arrow_right: Every Python package is one `pip` command away ! With its dependencies !
:arrow_right: Deploy almost everything almost everywhere (whatever your distro, environment, architecture...) !
:arrow_right: It is **relatively easy** to publish your packages, even complex ones.
.vspace[]
## .hcenter[:arrow_right: One Python project ? One virtual environment for its dependencies.]