From d2e7a630225dc5b5e097d30ef1c081b86ad6b4f9 Mon Sep 17 00:00:00 2001
From: Yannick DAYER <yannick.dayer@idiap.ch>
Date: Tue, 8 Nov 2022 18:56:05 +0100
Subject: [PATCH] Switch to the new package structure using citools.

---
 .gitignore                                    |  25 ++-
 .gitlab-ci.yml                                |   5 +-
 .pre-commit-config.yaml                       |  40 +++--
 MANIFEST.in                                   |   5 +-
 README.md                                     |  19 ++
 README.rst                                    |  41 -----
 buildout.cfg                                  |  13 --
 conda/meta.yaml                               |  39 ++---
 doc/conf.py                                   | 164 ++----------------
 doc/extra-intersphinx.txt                     |   1 -
 doc/index.rst                                 |   2 +-
 doc/install.rst                               |  38 ++++
 doc/links.rst                                 |  11 +-
 doc/nitpick-exceptions.txt                    |   6 -
 pyproject.toml                                |  79 ++++++++-
 requirements.txt                              |   5 -
 setup.py                                      |  46 +----
 {bob => src/bob}/__init__.py                  |   0
 {bob => src/bob}/io/__init__.py               |   0
 {bob => src/bob}/io/base/__init__.py          |  20 +--
 {bob => src/bob}/io/base/test_utils.py        |  13 +-
 {bob => src/bob}/io/image.py                  |  14 +-
 {bob/io/base/test => tests}/__init__.py       |   0
 {bob/io/base/test => tests}/data/cmyk.jpg     | Bin
 .../base/test => tests}/data/grace_hopper.png | Bin
 .../test => tests}/data/img_gray_alpha.png    | Bin
 .../test => tests}/data/img_indexed_color.png | Bin
 .../data/img_indexed_color_alpha.png          | Bin
 .../test => tests}/data/img_rgba_color.png    | Bin
 {bob/io/base/test => tests}/data/img_trns.png | Bin
 .../base/test => tests}/data/matlab_1d.hdf5   | Bin
 .../base/test => tests}/data/matlab_2d.hdf5   | Bin
 .../test => tests}/data/read_png_gray.png     | Bin
 {bob/io/base/test => tests}/data/test.gif     | Bin
 {bob/io/base/test => tests}/data/test.jpg     | Bin
 {bob/io/base/test => tests}/data/test.pbm     |   0
 {bob/io/base/test => tests}/data/test.pgm     | Bin
 {bob/io/base/test => tests}/data/test.ppm     | Bin
 {bob/io/base/test => tests}/data/test1.hdf5   | Bin
 .../test => tests}/data/test7_unlimited.hdf5  | Bin
 {bob/io/base/test => tests}/data/test_2.pgm   | Bin
 {bob/io/base/test => tests}/data/test_2.ppm   | Bin
 .../test => tests}/data/test_array_codec.txt  |   0
 .../test => tests}/data/test_corrupted.pbm    |   0
 .../test => tests}/data/test_corrupted.pgm    | Bin
 .../test => tests}/data/test_corrupted.ppm    | Bin
 .../base/test => tests}/data/test_spaces.pgm  |   0
 .../test => tests}/data/truncated_jpeg.jpg    | Bin
 {bob/io/base/test => tests}/test_hdf5.py      |   7 +-
 .../base/test => tests}/test_image_support.py |   6 +-
 {bob/io/base/test => tests}/test_io.py        |   1 -
 .../io/base/test => tests}/test_utlilities.py |   0
 version.txt                                   |   1 -
 53 files changed, 241 insertions(+), 360 deletions(-)
 create mode 100644 README.md
 delete mode 100644 README.rst
 delete mode 100644 buildout.cfg
 delete mode 100644 doc/extra-intersphinx.txt
 create mode 100644 doc/install.rst
 delete mode 100644 doc/nitpick-exceptions.txt
 delete mode 100644 requirements.txt
 rename {bob => src/bob}/__init__.py (100%)
 rename {bob => src/bob}/io/__init__.py (100%)
 rename {bob => src/bob}/io/base/__init__.py (97%)
 rename {bob => src/bob}/io/base/test_utils.py (94%)
 rename {bob => src/bob}/io/image.py (92%)
 rename {bob/io/base/test => tests}/__init__.py (100%)
 rename {bob/io/base/test => tests}/data/cmyk.jpg (100%)
 rename {bob/io/base/test => tests}/data/grace_hopper.png (100%)
 rename {bob/io/base/test => tests}/data/img_gray_alpha.png (100%)
 rename {bob/io/base/test => tests}/data/img_indexed_color.png (100%)
 rename {bob/io/base/test => tests}/data/img_indexed_color_alpha.png (100%)
 rename {bob/io/base/test => tests}/data/img_rgba_color.png (100%)
 rename {bob/io/base/test => tests}/data/img_trns.png (100%)
 rename {bob/io/base/test => tests}/data/matlab_1d.hdf5 (100%)
 rename {bob/io/base/test => tests}/data/matlab_2d.hdf5 (100%)
 rename {bob/io/base/test => tests}/data/read_png_gray.png (100%)
 rename {bob/io/base/test => tests}/data/test.gif (100%)
 rename {bob/io/base/test => tests}/data/test.jpg (100%)
 rename {bob/io/base/test => tests}/data/test.pbm (100%)
 rename {bob/io/base/test => tests}/data/test.pgm (100%)
 rename {bob/io/base/test => tests}/data/test.ppm (100%)
 rename {bob/io/base/test => tests}/data/test1.hdf5 (100%)
 rename {bob/io/base/test => tests}/data/test7_unlimited.hdf5 (100%)
 rename {bob/io/base/test => tests}/data/test_2.pgm (100%)
 rename {bob/io/base/test => tests}/data/test_2.ppm (100%)
 rename {bob/io/base/test => tests}/data/test_array_codec.txt (100%)
 rename {bob/io/base/test => tests}/data/test_corrupted.pbm (100%)
 rename {bob/io/base/test => tests}/data/test_corrupted.pgm (100%)
 rename {bob/io/base/test => tests}/data/test_corrupted.ppm (100%)
 rename {bob/io/base/test => tests}/data/test_spaces.pgm (100%)
 rename {bob/io/base/test => tests}/data/truncated_jpeg.jpg (100%)
 rename {bob/io/base/test => tests}/test_hdf5.py (92%)
 rename {bob/io/base/test => tests}/test_image_support.py (98%)
 rename {bob/io/base/test => tests}/test_io.py (99%)
 rename {bob/io/base/test => tests}/test_utlilities.py (100%)
 delete mode 100644 version.txt

diff --git a/.gitignore b/.gitignore
index bde2e54..9988549 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,20 +1,15 @@
 *~
 *.swp
 *.pyc
-*.so
-*.dylib
-bin
-eggs
-parts
-.installed.cfg
-.mr.developer.cfg
 *.egg-info
-develop-eggs
-sphinx
-dist
 .nfs*
-.gdb_history
-build
-*.egg
-src/
-record.txt
+.coverage
+*.DS_Store
+.envrc
+coverage.xml
+test_results.xml
+junit-coverage.xml
+html/
+build/
+doc/api/
+dist/
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 845b719..80ad328 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1 +1,4 @@
-include: 'https://gitlab.idiap.ch/bob/bob.devtools/raw/master/bob/devtools/data/gitlab-ci/single-package.yaml'
+include:
+  - project: bob/citools
+    ref: master
+    file: /src/citools/data/python.yml
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 6352dfc..f06ec38 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,27 +1,47 @@
 # See https://pre-commit.com for more information
 # See https://pre-commit.com/hooks.html for more hooks
 repos:
-  - repo: https://github.com/timothycrosley/isort
-    rev: 5.10.1
-    hooks:
-      - id: isort
-        args: [--settings-path, "pyproject.toml"]
   - repo: https://github.com/psf/black
-    rev: 22.3.0
+    rev: 22.10.0
     hooks:
       - id: black
+  - repo: https://github.com/pycqa/docformatter
+    rev: v1.5.0
+    hooks:
+      - id: docformatter
+  - repo: https://github.com/pycqa/isort
+    rev: 5.10.1
+    hooks:
+      - id: isort
   - repo: https://gitlab.com/pycqa/flake8
     rev: 3.9.2
     hooks:
       - id: flake8
+  - repo: https://github.com/pre-commit/mirrors-mypy
+    rev: v0.982
+    hooks:
+    - id: mypy
+      args: [
+        --install-types,
+        --non-interactive,
+        --no-strict-optional,
+        --ignore-missing-imports,
+      ]
+  - repo: https://github.com/asottile/pyupgrade
+    rev: v3.1.0
+    hooks:
+    - id: pyupgrade
+      args: [--py38-plus]
   - repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v4.2.0
+    rev: v4.3.0
     hooks:
       - id: check-ast
+      - id: check-added-large-files
+      - id: check-toml
+      - id: check-yaml
+        exclude: conda/meta.yaml
+      - id: debug-statements
       - id: check-case-conflict
       - id: trailing-whitespace
       - id: end-of-file-fixer
       - id: debug-statements
-      - id: check-added-large-files
-      - id: check-yaml
-        exclude: .*/meta.yaml
diff --git a/MANIFEST.in b/MANIFEST.in
index 89f15c6..4399a12 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,3 +1,2 @@
-include LICENSE README.rst buildout.cfg develop.cfg requirements.txt version.txt
-recursive-include doc conf.py *.rst
-recursive-include bob/io/base/test/data *.*
+recursive-include doc *.rst *.ico *.png
+recursive-include tests *.py
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..984091c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,19 @@
+[![latest-docs](https://img.shields.io/badge/docs-latest-orange.svg)](https://www.idiap.ch/software/bob/docs/bob/bob.io.base/master/sphinx/index.html)
+[![build](https://gitlab.idiap.ch/bob/bob.io.base/badges/master/pipeline.svg)](https://gitlab.idiap.ch/bob/bob.io.base/commits/master)
+[![coverage](https://gitlab.idiap.ch/bob/bob.io.base/badges/master/coverage.svg)](https://www.idiap.ch/software/bob/docs/bob/bob.io.base/master/coverage/index.html)
+[![repository](https://img.shields.io/badge/gitlab-project-0000c0.svg)](https://gitlab.idiap.ch/bob/bob.io.base)
+
+# bob.io.base
+
+This package is part of the signal-processing and machine learning toolbox
+[Bob](https://www.idiap.ch/software/bob). It contains Bob's basic I/O functionality.
+
+> :warning: **This is a deprecated package** that will be soon removed from bob. Use
+with caution.
+
+Contains utilities to load and save data files and adapt the format of images.
+
+Also contains some remnants of utility (notably for testing) functions that will need
+to be moved to a new package.
+
+For installation and usage instructions, check-out our documentation.
diff --git a/README.rst b/README.rst
deleted file mode 100644
index f9eab15..0000000
--- a/README.rst
+++ /dev/null
@@ -1,41 +0,0 @@
-.. vim: set fileencoding=utf-8 :
-.. Thu 11 Aug 15:13:11 CEST 2016
-
-.. image:: https://img.shields.io/badge/docs-latest-orange.svg
-   :target: https://www.idiap.ch/software/bob/docs/bob/bob.io.base/master/index.html
-.. image:: https://gitlab.idiap.ch/bob/bob.io.base/badges/master/pipeline.svg
-   :target: https://gitlab.idiap.ch/bob/bob.io.base/commits/master
-.. image:: https://gitlab.idiap.ch/bob/bob.io.base/badges/master/coverage.svg
-   :target: https://gitlab.idiap.ch/bob/bob.io.base/commits/master
-.. image:: https://img.shields.io/badge/gitlab-project-0000c0.svg
-   :target: https://gitlab.idiap.ch/bob/bob.io.base
-
-
-=========================================
- Basic Input/Output functionality of Bob
-=========================================
-
-This package is part of the signal-processing and machine learning toolbox
-Bob_. It contains Bob's basic I/O functionality.
-
-
-Installation
-------------
-
-Complete Bob's `installation`_ instructions. Then, to install this package,
-run::
-
-  $ conda install bob.io.base
-
-
-Contact
--------
-
-For questions or reporting issues to this software package, contact our
-development `mailing list`_.
-
-
-.. Place your references here:
-.. _bob: https://www.idiap.ch/software/bob
-.. _installation: https://www.idiap.ch/software/bob/install
-.. _mailing list: https://www.idiap.ch/software/bob/discuss
diff --git a/buildout.cfg b/buildout.cfg
deleted file mode 100644
index 0fa626f..0000000
--- a/buildout.cfg
+++ /dev/null
@@ -1,13 +0,0 @@
-; vim: set fileencoding=utf-8 :
-; Thu 11 Aug 15:12:05 CEST 2016
-
-[buildout]
-parts = scripts
-develop = .
-eggs = bob.io.base
-extensions = bob.buildout
-newest = false
-verbose = true
-
-[scripts]
-recipe = bob.buildout:scripts
diff --git a/conda/meta.yaml b/conda/meta.yaml
index a70a3cf..19970f4 100644
--- a/conda/meta.yaml
+++ b/conda/meta.yaml
@@ -1,27 +1,25 @@
-{% set name = 'bob.io.base' %}
-{% set project_dir = environ.get('RECIPE_DIR') + '/..' %}
+{% set data = load_file_data(RECIPE_DIR + '/../pyproject.toml') %}
 
 package:
-  name: {{ name }}
-  version: {{ environ.get('BOB_PACKAGE_VERSION', '0.0.1') }}
+  name: {{ data['project']['name'] }}
+  version: {{ data['project']['version'] }}
+
+source:
+  path: ..
 
 build:
+  noarch: python
   number: {{ environ.get('BOB_BUILD_NUMBER', 0) }}
   run_exports:
-    - {{ pin_subpackage(name) }}
+    - {{ pin_subpackage(data['project']['name']) }}
   script:
-    - cd {{ project_dir }}
-    {% if environ.get('BUILD_EGG') %}
-    - "{{ PYTHON }} setup.py sdist --formats=zip"
-    {% endif %}
-    - "{{ PYTHON }} -m pip install . -vv"
+    - "{{ PYTHON }} -m pip install {{ SRC_DIR }} -vv"
 
 requirements:
   host:
     - python {{ python }}
     - setuptools {{ setuptools }}
     - pip {{ pip }}
-    - bob.extension
     - h5py {{ h5py }}
     - numpy {{ numpy }}
     - pillow {{ pillow }}
@@ -37,23 +35,16 @@ requirements:
     - {{ pin_compatible('click') }}
 
 test:
+  source_files:
+    - tests
   imports:
-    - {{ name }}
+    - {{ data['project']['name'].replace('-','_') }}
   commands:
-    - pytest --verbose --cov {{ name }} --cov-report term-missing --cov-report html:{{ project_dir }}/sphinx/coverage --cov-report xml:{{ project_dir }}/coverage.xml --pyargs {{ name }}
-    - sphinx-build -aEW {{ project_dir }}/doc {{ project_dir }}/sphinx
-    - sphinx-build -aEb doctest {{ project_dir }}/doc sphinx
     - conda inspect linkages -p $PREFIX {{ name }}  # [not win]
     - conda inspect objects -p $PREFIX {{ name }}  # [osx]
-  requires:
-    - pytest {{ pytest }}
-    - pytest-cov {{ pytest_cov }}
-    - coverage {{ coverage }}
-    - sphinx {{ sphinx }}
-    - sphinx_rtd_theme {{ sphinx_rtd_theme }}
 
 about:
-  home: https://www.idiap.ch/software/bob/
-  license: BSD 3-Clause
-  summary: Basic IO for Bob
+  home: {{ data['project']['urls']['homepage'] }}
+  summary: {{ data['project']['description'] }}
+  license: {{ data['project']['license']['text'] }}
   license_family: BSD
diff --git a/doc/conf.py b/doc/conf.py
index 5d4e765..319edc9 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -1,9 +1,7 @@
-#!/usr/bin/env python
-# vim: set fileencoding=utf-8 :
-
 import os
+import time
 
-import pkg_resources
+from importlib.metadata import distribution
 
 # -- General configuration -----------------------------------------------------
 
@@ -14,20 +12,16 @@ needs_sphinx = "1.3"
 # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
 extensions = [
     "sphinx.ext.todo",
-    "sphinx.ext.coverage",
-    "sphinx.ext.ifconfig",
     "sphinx.ext.autodoc",
     "sphinx.ext.autosummary",
     "sphinx.ext.doctest",
-    "sphinx.ext.graphviz",
-    "sphinx.ext.intersphinx",
     "sphinx.ext.napoleon",
     "sphinx.ext.viewcode",
-    "sphinx.ext.mathjax",
+    "sphinx.ext.intersphinx",
 ]
 
 # Be picky about warnings
-nitpicky = False
+nitpicky = True
 
 # Ignores stuff we can't easily resolve on other project's sphinx manuals
 nitpick_ignore = []
@@ -39,10 +33,6 @@ if os.path.exists("nitpick-exceptions.txt"):
             continue
         dtype, target = line.split(None, 1)
         target = target.strip()
-        try:  # python 2.x
-            target = unicode(target)
-        except NameError:
-            pass
         nitpick_ignore.append((dtype, target))
 
 # Always includes todos
@@ -55,7 +45,7 @@ autosummary_generate = True
 numfig = True
 
 # If we are on OSX, the 'dvipng' path maybe different
-dvipng_osx = "/opt/local/libexec/texlive/binaries/dvipng"
+dvipng_osx = "/Library/TeX/texbin/dvipng"
 if os.path.exists(dvipng_osx):
     pngmath_dvipng = dvipng_osx
 
@@ -65,165 +55,54 @@ templates_path = ["_templates"]
 # The suffix of source filenames.
 source_suffix = ".rst"
 
-# The encoding of source files.
-# source_encoding = 'utf-8-sig'
-
 # The master toctree document.
 master_doc = "index"
 
 # General information about the project.
 project = "bob.io.base"
-import time
+package = distribution(project)
 
 copyright = "%s, Idiap Research Institute" % time.strftime("%Y")
 
-# Grab the setup entry
-distribution = pkg_resources.require(project)[0]
-
-# The version info for the project you're documenting, acts as replacement for
-# |version| and |release|, also used in various other places throughout the
-# built documents.
-#
 # The short X.Y version.
-version = distribution.version
+version = package.version
 # The full version, including alpha/beta/rc tags.
-release = distribution.version
-
-# The language for content autogenerated by Sphinx. Refer to documentation
-# for a list of supported languages.
-# language = None
-
-# There are two options for replacing |today|: either, you set today to some
-# non-false value, then it is used:
-# today = ''
-# Else, today_fmt is used as the format for a strftime call.
-# today_fmt = '%B %d, %Y'
+release = version
 
 # List of patterns, relative to source directory, that match files and
 # directories to ignore when looking for source files.
 exclude_patterns = ["links.rst"]
 
-# The reST default role (used for this markup: `text`) to use for all documents.
-# default_role = None
-
-# If true, '()' will be appended to :func: etc. cross-reference text.
-# add_function_parentheses = True
-
-# If true, the current module name will be prepended to all description
-# unit titles (such as .. function::).
-# add_module_names = True
-
-# If true, sectionauthor and moduleauthor directives will be shown in the
-# output. They are ignored by default.
-# show_authors = False
-
 # The name of the Pygments (syntax highlighting) style to use.
 pygments_style = "sphinx"
+pygments_dark_style = "monokai"
 
 # A list of ignored prefixes for module index sorting.
 # modindex_common_prefix = []
 
 # Some variables which are useful for generated material
 project_variable = project.replace(".", "_")
-short_description = "Basic IO for Bob"
+short_description = package.metadata["Summary"]
 owner = ["Idiap Research Institute"]
 
-
 # -- Options for HTML output ---------------------------------------------------
 
-# The theme to use for HTML and HTML Help pages.  See the documentation for
-# a list of builtin themes.
-import sphinx_rtd_theme
 
 html_theme = "sphinx_rtd_theme"
 
-# Theme options are theme-specific and customize the look and feel of a theme
-# further.  For a list of options available for each theme, see the
-# documentation.
-# html_theme_options = {}
-
-# Add any paths that contain custom themes here, relative to this directory.
-html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
+html_theme_options = {
+    "source_edit_link": f"https://gitlab.idiap.ch/bob/{project}/-/edit/master/doc/{{filename}}",
+}
 
-# The name for this set of Sphinx documents.  If None, it defaults to
-# "<project> v<release> documentation".
-# html_title = None
+html_title = f"{project} {release}"
 
-# A shorter title for the navigation bar.  Default is the same as html_title.
-# html_short_title = project_variable
 
-# The name of an image file (relative to this directory) to place at the top
-# of the sidebar.
 html_logo = "img/logo.png"
-
-# The name of an image file (within the static path) to use as favicon of the
-# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
-# pixels large.
 html_favicon = "img/favicon.ico"
 
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-# html_static_path = ['_static']
-
-# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
-# using the given strftime format.
-# html_last_updated_fmt = '%b %d, %Y'
-
-# If true, SmartyPants will be used to convert quotes and dashes to
-# typographically correct entities.
-# html_use_smartypants = True
-
-# Custom sidebar templates, maps document names to template names.
-# html_sidebars = {}
-
-# Additional templates that should be rendered to pages, maps page names to
-# template names.
-# html_additional_pages = {}
-
-# If false, no module index is generated.
-# html_domain_indices = True
-
-# If false, no index is generated.
-# html_use_index = True
-
-# If true, the index is split into individual pages for each letter.
-# html_split_index = False
-
-# If true, links to the reST sources are added to the pages.
-# html_show_sourcelink = True
-
-# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
-# html_show_sphinx = True
-
-# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
-# html_show_copyright = True
-
-# If true, an OpenSearch description file will be output, and all pages will
-# contain a <link> tag referring to it.  The value of this option must be the
-# base URL from which the finished HTML is served.
-# html_use_opensearch = ''
-
-# This is the file name suffix for HTML files (e.g. ".xhtml").
-# html_file_suffix = None
-
-# Output file base name for HTML help builder.
-htmlhelp_basename = project_variable + "_doc"
-
-
 # -- Post configuration --------------------------------------------------------
 
-# Included after all input documents
-rst_epilog = """
-.. |project| replace:: Bob
-.. |version| replace:: %s
-.. |current-year| date:: %%Y
-""" % (
-    version,
-)
-
 # Default processing flags for sphinx
-autoclass_content = "class"
 autodoc_member_order = "bysource"
 autodoc_default_options = {
     "members": True,
@@ -231,14 +110,7 @@ autodoc_default_options = {
     "show-inheritance": True,
 }
 
-# For inter-documentation mapping:
-from bob.extension.utils import link_documentation, load_requirements
-
-sphinx_requirements = "extra-intersphinx.txt"
-if os.path.exists(sphinx_requirements):
-    intersphinx_mapping = link_documentation(
-        additional_packages=["python", "numpy"]
-        + load_requirements(sphinx_requirements)
-    )
-else:
-    intersphinx_mapping = link_documentation()
+intersphinx_mapping = {
+    "python": ("https://docs.python.org/3", None),
+    "scipy": ("https://docs.scipy.org/doc/scipy/", None),
+}
diff --git a/doc/extra-intersphinx.txt b/doc/extra-intersphinx.txt
deleted file mode 100644
index 9a635b9..0000000
--- a/doc/extra-intersphinx.txt
+++ /dev/null
@@ -1 +0,0 @@
-scipy
diff --git a/doc/index.rst b/doc/index.rst
index 3e0e720..df797ed 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -25,12 +25,12 @@ Documentation
 .. toctree::
    :maxdepth: 2
 
+   install
    py_api
 
 TODO
 ----
 
-.. todolist::
 
 Indices and tables
 ------------------
diff --git a/doc/install.rst b/doc/install.rst
new file mode 100644
index 0000000..efe080a
--- /dev/null
+++ b/doc/install.rst
@@ -0,0 +1,38 @@
+.. _bob.io.base.install:
+
+==============
+ Installation
+==============
+
+We support two installation modes, through pip_, or mamba_ (conda).
+
+With pip
+--------
+
+.. code-block:: sh
+
+   # stable, from PyPI:
+   $ pip install bob.io.base
+
+   # latest beta, from GitLab package registry:
+   $ pip install --pre --index-url https://gitlab.idiap.ch/api/v4/groups/bob/-/packages/pypi/simple --extra-index-url https://pypi.org/simple bob.io.base
+
+.. tip::
+
+   To avoid long command-lines you may configure pip to define the indexes and
+   package search priorities as you like.
+
+
+With conda
+----------
+
+.. code-block:: sh
+
+   # stable:
+   $ mamba install -c https://www.idiap.ch/software/bob/conda -c conda-forge bob.io.base
+
+   # latest beta:
+   $ mamba install -c https://www.idiap.ch/software/bob/conda/label/beta -c conda-forge bob.io.base
+
+
+.. include:: links.rst
diff --git a/doc/links.rst b/doc/links.rst
index 5c4125f..9a0a738 100644
--- a/doc/links.rst
+++ b/doc/links.rst
@@ -1,7 +1,8 @@
-.. vim: set fileencoding=utf-8 :
+.. place re-used URLs here, then include this file
+.. on your other RST sources.
 
-.. _blitz++: http://www.oonumerics.org/blitz
-.. _bob: https://www.idiap.ch/software/bob
-.. _hdf5: http://www.hdfgroup.org/HDF5
-.. _numpy: http://numpy.scipy.org
+.. _conda: https://conda.io
+.. _idiap: http://www.idiap.ch
 .. _python: http://www.python.org
+.. _pip: https://pip.pypa.io/en/stable/
+.. _mamba: https://mamba.readthedocs.io/en/latest/index.html
diff --git a/doc/nitpick-exceptions.txt b/doc/nitpick-exceptions.txt
deleted file mode 100644
index f61551f..0000000
--- a/doc/nitpick-exceptions.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-# exceptions are not found in python 2.7
-py:exc RuntimeError
-py:class tuple
-
-# these don't exists on numpy's manual
-py:class numpy.uint8
diff --git a/pyproject.toml b/pyproject.toml
index b738dc8..890e354 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,12 +1,79 @@
 [build-system]
-    requires = ["setuptools", "wheel", "bob.extension"]
+    requires = ["setuptools>=61.0.0", "wheel"]
     build-backend = "setuptools.build_meta"
 
+[project]
+name = "bob.io.base"
+version = "5.0.3b1"
+requires-python = ">=3.9"
+description = "Basic IO for Bob"
+dynamic = ["readme"]
+license = {text = "BSD 3-Clause License"}
+authors = [
+  {name = "Andre Anjos"},
+  {email = "andre.anjos@idiap.ch"},
+]
+classifiers = [
+    "Development Status :: 4 - Beta",
+    "Intended Audience :: Developers",
+    "License :: OSI Approved :: BSD License",
+    "Natural Language :: English",
+    "Programming Language :: Python :: 3",
+    "Topic :: Software Development :: Libraries :: Python Modules",
+]
+dependencies = [
+    "h5py",
+    "imageio",
+    "numpy",
+    "click",
+    "pillow",
+]
+
+[project.urls]
+documentation = "https://www.idiap.ch/software/bob/docs/bob/bob.io.base/master/"
+homepage = "https://pypi.org/project/bob.io.base"
+repository = "https://gitlab.idiap.ch/bob/bob.io.base"
+changelog = "https://gitlab.idiap.ch/bob/bob.io.base/-/releases"
+
+[project.optional-dependencies]
+qa = ["pre-commit"]
+doc = [
+    "sphinx",
+    "sphinx_rtd_theme",
+    ]
+test = [
+    "pytest",
+    "pytest-cov",
+    "coverage",
+    ]
+
+[tool.setuptools]
+zip-safe = true
+package-dir = {"" = "src"}
+
+[tool.setuptools.dynamic]
+readme = {file = "README.md"}
+
+[tool.distutils.bdist_wheel]
+universal = true
+
 [tool.isort]
-    profile = "black"
-    line_length = 80
-    order_by_type = true
-    lines_between_types = 1
+profile = "black"
+line_length = 80
+order_by_type = true
+lines_between_types = 1
 
 [tool.black]
-    line-length = 80
+line-length = 80
+
+[tool.coverage.run]
+relative_files = true
+
+[tool.pytest.ini_options]
+addopts = [
+    "--import-mode=append",
+    "--cov-report=term-missing",
+    "--cov=bob.io.base",
+]
+junit_logging = "all"
+junit_log_passing_tests = false
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index ae1697a..0000000
--- a/requirements.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-bob.extension
-h5py
-imageio
-numpy
-click
diff --git a/setup.py b/setup.py
index b435850..6068493 100644
--- a/setup.py
+++ b/setup.py
@@ -1,45 +1,3 @@
-#!/usr/bin/env python
-# vim: set fileencoding=utf-8 :
-# Andre Anjos <andre.anjos@idiap.ch>
-# Mon 16 Apr 08:18:08 2012 CEST
-#
-from setuptools import dist, setup
+from setuptools import setup
 
-dist.Distribution(dict(setup_requires=["bob.extension"]))
-
-from bob.extension.utils import find_packages, load_requirements
-
-install_requires = load_requirements()
-
-# The only thing we do in this file is to call the setup() function with all
-# parameters that define our package.
-setup(
-    # This is the basic information about your project. Modify all this
-    # information before releasing code publicly.
-    name="bob.io.base",
-    version=open("version.txt").read().rstrip(),
-    description="Basic IO for Bob",
-    url="http://gitlab.idiap.ch/bob/bob.io.base",
-    license="BSD",
-    author="Andre Anjos",
-    author_email="andre.anjos@idiap.ch",
-    long_description=open("README.rst").read(),
-    packages=find_packages(),
-    include_package_data=True,
-    zip_safe=False,
-    setup_requires=install_requires,
-    install_requires=install_requires,
-    # Classifiers are important if you plan to distribute this package through
-    # PyPI. You can find the complete list of classifiers that are valid and
-    # useful here (http://pypi.python.org/pypi?%3Aaction=list_classifiers).
-    classifiers=[
-        "Framework :: Bob",
-        "Development Status :: 4 - Beta",
-        "Intended Audience :: Developers",
-        "License :: OSI Approved :: BSD License",
-        "Natural Language :: English",
-        "Programming Language :: Python",
-        "Programming Language :: Python :: 3",
-        "Topic :: Software Development :: Libraries :: Python Modules",
-    ],
-)
+setup()
diff --git a/bob/__init__.py b/src/bob/__init__.py
similarity index 100%
rename from bob/__init__.py
rename to src/bob/__init__.py
diff --git a/bob/io/__init__.py b/src/bob/io/__init__.py
similarity index 100%
rename from bob/io/__init__.py
rename to src/bob/io/__init__.py
diff --git a/bob/io/base/__init__.py b/src/bob/io/base/__init__.py
similarity index 97%
rename from bob/io/base/__init__.py
rename to src/bob/io/base/__init__.py
index 5b1d23c..b1527d0 100644
--- a/bob/io/base/__init__.py
+++ b/src/bob/io/base/__init__.py
@@ -38,10 +38,11 @@ def _is_string(s):
 
 @np.deprecate(new_name="os.makedirs(directory, exist_ok=True)")
 def create_directories_safe(directory, dryrun=False):
-    """Creates a directory if it does not exists, with concurrent access support.
-    This function will also create any parent directories that might be required.
-    If the dryrun option is selected, it does not actually create the directory,
-    but just writes the (Linux) command that would have been executed.
+    """Creates a directory if it does not exists, with concurrent access
+    support. This function will also create any parent directories that might
+    be required. If the dryrun option is selected, it does not actually create
+    the directory, but just writes the (Linux) command that would have been
+    executed.
 
     **Parameters:**
 
@@ -58,7 +59,7 @@ def create_directories_safe(directory, dryrun=False):
 
 
 def open_file(filename):
-    """open_file(filename) -> file
+    """open_file(filename) -> file.
 
     Opens a file for reading.
 
@@ -67,8 +68,6 @@ def open_file(filename):
 
     ``filename`` : str
       The name of the file to open.
-
-
     """
 
     def check_gray(img):
@@ -120,8 +119,7 @@ def open_file(filename):
 
 
 def write_file(filename, data, format="pillow"):
-    """
-    write_file(filename, data) -> None
+    """write_file(filename, data) -> None.
 
     Writes the contents of a :py:class:`numpy.ndarray` to a file.
 
@@ -136,8 +134,6 @@ def write_file(filename, data, format="pillow"):
 
     ``format`` : str
       The format to use to read the file. By default imageio selects the appropriate for you based on the filename and its contents
-
-
     """
 
     extension = os.path.splitext(filename)[1]  # get the extension
@@ -155,7 +151,7 @@ def write_file(filename, data, format="pillow"):
 
 
 def load(inputs):
-    """load(inputs) -> data
+    """load(inputs) -> data.
 
     Loads the contents of a file, an iterable of files, or an iterable of
     :py:class:`bob.io.base.File`'s into a :py:class:`numpy.ndarray`.
diff --git a/bob/io/base/test_utils.py b/src/bob/io/base/test_utils.py
similarity index 94%
rename from bob/io/base/test_utils.py
rename to src/bob/io/base/test_utils.py
index 193b323..1539ec6 100644
--- a/bob/io/base/test_utils.py
+++ b/src/bob/io/base/test_utils.py
@@ -1,12 +1,10 @@
 #!/usr/bin/env python
-# vim: set fileencoding=utf-8 :
 # Andre Anjos <andre.anjos@idiap.ch>
 # Thu Feb  7 09:58:22 2013
 #
 # Copyright (C) 2011-2013 Idiap Research Institute, Martigny, Switzerland
 
-"""Re-usable decorators and utilities for bob test code
-"""
+"""Re-usable decorators and utilities for bob test code."""
 
 import functools
 import os
@@ -19,7 +17,7 @@ import click.testing
 
 
 def datafile(f, module=None, path="data"):
-    """datafile(f, [module], [data]) -> filename
+    """datafile(f, [module], [data]) -> filename.
 
     Returns the test file on the "data" subdirectory of the current module.
 
@@ -52,7 +50,7 @@ def datafile(f, module=None, path="data"):
 
 
 def temporary_filename(prefix="bobtest_", suffix=".hdf5"):
-    """temporary_filename([prefix], [suffix]) -> filename
+    """temporary_filename([prefix], [suffix]) -> filename.
 
     Generates a temporary filename to be used in tests, using the default ``temp`` directory (on Unix-like systems, usually ``/tmp``).
     Please note that you are responsible for deleting the file after your test finished.
@@ -81,7 +79,6 @@ def temporary_filename(prefix="bobtest_", suffix=".hdf5"):
     ``filename`` : str
       The name of a temporary file that you can use in your test.
       Don't forget to delete!
-
     """
     import tempfile
 
@@ -92,7 +89,7 @@ def temporary_filename(prefix="bobtest_", suffix=".hdf5"):
 
 
 def extension_available(extension):
-    """Decorator to check if a extension is available before enabling a test
+    """Decorator to check if a extension is available before enabling a test.
 
     This decorator is mainly used to decorate a test function, in order to skip tests when the extension is not available.
     The syntax is:
@@ -127,7 +124,7 @@ def extension_available(extension):
 def assert_click_runner_result(
     result: click.testing.Result,
     exit_code: int = 0,
-    exception_type: Optional[Exception] = None,
+    exception_type: Optional[type] = None,
 ):
     """Helper for asserting click runner results.
 
diff --git a/bob/io/image.py b/src/bob/io/image.py
similarity index 92%
rename from bob/io/image.py
rename to src/bob/io/image.py
index 682a454..a6a8be3 100644
--- a/bob/io/image.py
+++ b/src/bob/io/image.py
@@ -4,8 +4,8 @@ from PIL import Image
 
 
 def to_matplotlib(img):
-    """Returns a view of the image from Bob format to matplotlib format.
-    This function works with images, batches of images, videos, and higher
+    """Returns a view of the image from Bob format to matplotlib format. This
+    function works with images, batches of images, videos, and higher
     dimensional arrays that contain images.
 
     Parameters
@@ -27,8 +27,8 @@ def to_matplotlib(img):
 
 
 def to_bob(img):
-    """Returns a view of the image from matplotlib format to Bob format.
-    This function works with images, batches of images, videos, and higher
+    """Returns a view of the image from matplotlib format to Bob format. This
+    function works with images, batches of images, videos, and higher
     dimensional arrays that contain images.
 
     Parameters
@@ -74,7 +74,7 @@ def bob_to_pillow(img):
 
 
 def pillow_to_bob(img):
-    """Converts an RGB or gray-scale pillow image to Bob format
+    """Converts an RGB or gray-scale pillow image to Bob format.
 
     Parameters
     ----------
@@ -116,8 +116,8 @@ def opencvbgr_to_bob(img):
 
 
 def bob_to_opencvbgr(img):
-    """Returns a view of the image from Bob format to OpenCV BGR format.
-    This function works with images, batches of images, videos, and higher
+    """Returns a view of the image from Bob format to OpenCV BGR format. This
+    function works with images, batches of images, videos, and higher
     dimensional arrays that contain images.
 
     Parameters
diff --git a/bob/io/base/test/__init__.py b/tests/__init__.py
similarity index 100%
rename from bob/io/base/test/__init__.py
rename to tests/__init__.py
diff --git a/bob/io/base/test/data/cmyk.jpg b/tests/data/cmyk.jpg
similarity index 100%
rename from bob/io/base/test/data/cmyk.jpg
rename to tests/data/cmyk.jpg
diff --git a/bob/io/base/test/data/grace_hopper.png b/tests/data/grace_hopper.png
similarity index 100%
rename from bob/io/base/test/data/grace_hopper.png
rename to tests/data/grace_hopper.png
diff --git a/bob/io/base/test/data/img_gray_alpha.png b/tests/data/img_gray_alpha.png
similarity index 100%
rename from bob/io/base/test/data/img_gray_alpha.png
rename to tests/data/img_gray_alpha.png
diff --git a/bob/io/base/test/data/img_indexed_color.png b/tests/data/img_indexed_color.png
similarity index 100%
rename from bob/io/base/test/data/img_indexed_color.png
rename to tests/data/img_indexed_color.png
diff --git a/bob/io/base/test/data/img_indexed_color_alpha.png b/tests/data/img_indexed_color_alpha.png
similarity index 100%
rename from bob/io/base/test/data/img_indexed_color_alpha.png
rename to tests/data/img_indexed_color_alpha.png
diff --git a/bob/io/base/test/data/img_rgba_color.png b/tests/data/img_rgba_color.png
similarity index 100%
rename from bob/io/base/test/data/img_rgba_color.png
rename to tests/data/img_rgba_color.png
diff --git a/bob/io/base/test/data/img_trns.png b/tests/data/img_trns.png
similarity index 100%
rename from bob/io/base/test/data/img_trns.png
rename to tests/data/img_trns.png
diff --git a/bob/io/base/test/data/matlab_1d.hdf5 b/tests/data/matlab_1d.hdf5
similarity index 100%
rename from bob/io/base/test/data/matlab_1d.hdf5
rename to tests/data/matlab_1d.hdf5
diff --git a/bob/io/base/test/data/matlab_2d.hdf5 b/tests/data/matlab_2d.hdf5
similarity index 100%
rename from bob/io/base/test/data/matlab_2d.hdf5
rename to tests/data/matlab_2d.hdf5
diff --git a/bob/io/base/test/data/read_png_gray.png b/tests/data/read_png_gray.png
similarity index 100%
rename from bob/io/base/test/data/read_png_gray.png
rename to tests/data/read_png_gray.png
diff --git a/bob/io/base/test/data/test.gif b/tests/data/test.gif
similarity index 100%
rename from bob/io/base/test/data/test.gif
rename to tests/data/test.gif
diff --git a/bob/io/base/test/data/test.jpg b/tests/data/test.jpg
similarity index 100%
rename from bob/io/base/test/data/test.jpg
rename to tests/data/test.jpg
diff --git a/bob/io/base/test/data/test.pbm b/tests/data/test.pbm
similarity index 100%
rename from bob/io/base/test/data/test.pbm
rename to tests/data/test.pbm
diff --git a/bob/io/base/test/data/test.pgm b/tests/data/test.pgm
similarity index 100%
rename from bob/io/base/test/data/test.pgm
rename to tests/data/test.pgm
diff --git a/bob/io/base/test/data/test.ppm b/tests/data/test.ppm
similarity index 100%
rename from bob/io/base/test/data/test.ppm
rename to tests/data/test.ppm
diff --git a/bob/io/base/test/data/test1.hdf5 b/tests/data/test1.hdf5
similarity index 100%
rename from bob/io/base/test/data/test1.hdf5
rename to tests/data/test1.hdf5
diff --git a/bob/io/base/test/data/test7_unlimited.hdf5 b/tests/data/test7_unlimited.hdf5
similarity index 100%
rename from bob/io/base/test/data/test7_unlimited.hdf5
rename to tests/data/test7_unlimited.hdf5
diff --git a/bob/io/base/test/data/test_2.pgm b/tests/data/test_2.pgm
similarity index 100%
rename from bob/io/base/test/data/test_2.pgm
rename to tests/data/test_2.pgm
diff --git a/bob/io/base/test/data/test_2.ppm b/tests/data/test_2.ppm
similarity index 100%
rename from bob/io/base/test/data/test_2.ppm
rename to tests/data/test_2.ppm
diff --git a/bob/io/base/test/data/test_array_codec.txt b/tests/data/test_array_codec.txt
similarity index 100%
rename from bob/io/base/test/data/test_array_codec.txt
rename to tests/data/test_array_codec.txt
diff --git a/bob/io/base/test/data/test_corrupted.pbm b/tests/data/test_corrupted.pbm
similarity index 100%
rename from bob/io/base/test/data/test_corrupted.pbm
rename to tests/data/test_corrupted.pbm
diff --git a/bob/io/base/test/data/test_corrupted.pgm b/tests/data/test_corrupted.pgm
similarity index 100%
rename from bob/io/base/test/data/test_corrupted.pgm
rename to tests/data/test_corrupted.pgm
diff --git a/bob/io/base/test/data/test_corrupted.ppm b/tests/data/test_corrupted.ppm
similarity index 100%
rename from bob/io/base/test/data/test_corrupted.ppm
rename to tests/data/test_corrupted.ppm
diff --git a/bob/io/base/test/data/test_spaces.pgm b/tests/data/test_spaces.pgm
similarity index 100%
rename from bob/io/base/test/data/test_spaces.pgm
rename to tests/data/test_spaces.pgm
diff --git a/bob/io/base/test/data/truncated_jpeg.jpg b/tests/data/truncated_jpeg.jpg
similarity index 100%
rename from bob/io/base/test/data/truncated_jpeg.jpg
rename to tests/data/truncated_jpeg.jpg
diff --git a/bob/io/base/test/test_hdf5.py b/tests/test_hdf5.py
similarity index 92%
rename from bob/io/base/test/test_hdf5.py
rename to tests/test_hdf5.py
index a5fa23b..3caeac9 100644
--- a/bob/io/base/test/test_hdf5.py
+++ b/tests/test_hdf5.py
@@ -1,11 +1,9 @@
 #!/usr/bin/env python
-# vim: set fileencoding=utf-8 :
 # Tiago de Freitas Pereira <tiago.pereira@idiap.ch>
 #
 # Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland
 
-"""Tests for the base HDF5 infrastructure
-"""
+"""Tests for the base HDF5 infrastructure."""
 
 import random
 import tempfile
@@ -16,7 +14,7 @@ from bob.io.base import load, save
 
 
 def read_write_check(data, numpy_assert=True):
-    """Testing loading and save different file types"""
+    """Testing loading and save different file types."""
 
     with tempfile.NamedTemporaryFile(prefix="bobtest_", suffix=".hdf5") as f:
         save(data, f.name)
@@ -28,7 +26,6 @@ def read_write_check(data, numpy_assert=True):
 
 
 def test_type_support():
-
     # This test will go through all supported types for reading/writing data
     # from to HDF5 files. One single file will hold all data for this test.
     # This is also supported with HDF5: multiple variables in a single file.
diff --git a/bob/io/base/test/test_image_support.py b/tests/test_image_support.py
similarity index 98%
rename from bob/io/base/test/test_image_support.py
rename to tests/test_image_support.py
index 78f3cd2..7bbaeec 100644
--- a/bob/io/base/test/test_image_support.py
+++ b/tests/test_image_support.py
@@ -1,12 +1,10 @@
 #!/usr/bin/env python
-# vim: set fileencoding=utf-8 :
 # Laurent El Shafey <laurent.el-shafey@idiap.ch>
 # Wed Aug 14 12:27:57 CEST 2013
 #
 # Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland
 
-"""Runs some image tests
-"""
+"""Runs some image tests."""
 
 import os
 
@@ -71,7 +69,6 @@ def test_png_gray_alpha():
 
 
 def transcode(filename):
-
     tmpname = temporary_filename(suffix=os.path.splitext(filename)[1])
     tmpnam_ = temporary_filename(suffix=os.path.splitext(filename)[1])
 
@@ -106,7 +103,6 @@ def transcode(filename):
 
 
 def test_netpbm():
-
     transcode(datafile("test.pbm", __name__))  # indexed, works fine
     transcode(datafile("test.pgm", __name__))  # indexed, works fine
     transcode(datafile("test.ppm", __name__))  # indexed, works fine
diff --git a/bob/io/base/test/test_io.py b/tests/test_io.py
similarity index 99%
rename from bob/io/base/test/test_io.py
rename to tests/test_io.py
index de57e65..f3fddf9 100644
--- a/bob/io/base/test/test_io.py
+++ b/tests/test_io.py
@@ -9,7 +9,6 @@ from ..test_utils import temporary_filename
 
 
 def test_io_vstack():
-
     paths = [1, 2, 3, 4, 5]
 
     def oracle(reader, paths):
diff --git a/bob/io/base/test/test_utlilities.py b/tests/test_utlilities.py
similarity index 100%
rename from bob/io/base/test/test_utlilities.py
rename to tests/test_utlilities.py
diff --git a/version.txt b/version.txt
deleted file mode 100644
index 92e3ec3..0000000
--- a/version.txt
+++ /dev/null
@@ -1 +0,0 @@
-5.0.3b0
-- 
GitLab