Skip to content

Commit

Permalink
Clean up package boilerplate (#149)
Browse files Browse the repository at this point in the history
This PR does a couple of things to clean up the boilerplate related to
packaging OMLT, see sections below for detailed explanations of the
changes.

* Remove `setup.cfg` , `setup.py`, `docs/requirements.txt`, `tox.ini` in
favour of `pyproject.toml`.
* Place `conda` requirements into `environment.yml` 
* Create new workflows `tests.yml` and `publish_release.yml`
* Add quality checks using `ruff`, `mypy`, `doctest`
* Use `just` for developer experience
* Updated the `Development` section of `README`  to talk about `just` 
* Clean up `conf.py`
* Move `pull_request_template.md`
* Allow publishing of package to pypi by pushing a new version tag

# Other comments

* consider internal package structure
* force squash merge of PRs - this keeps git history for the `main`
branch nice and clean

# Using `pyproject.toml`

`pyrpoject.toml` is the simplest way to provide package metadata for a
Python package. It is easy to read and also provides sections for
configurating tools such as `pytest`, `ruff` and `mypy` all in one
place. It works seamlessly with the modern Python ecosystem.

I set up `pyproject.toml` to automactically detect the version of the
code from git tags. No need to duplicate version numbers across the
repo. Just add a new tag and everything will be updated. In addition,
when a new git tag is pushed to the GitHub repo, the new
`publish_release` workflow will be triggered and a new PYPI version
released. (See more on this below).

I also set it up so that the version is automatically added to a file
called `src/omlt/_version.py` which holds the `__version__` variable.
this file is autogenerated and therefore added to `.gitignore`. The
`__version__` veriable is then re-exported in `src/omlt/__init__.py` so
that our users have access to it.

I tried to perserve all the information stored in the `setup.cfg` and
other deleted files -- let me know if there is something i missed!

## Optional dependencies

The `pyproject.toml` file allows the creation of optional dependencies.
For example, our users can install

```bash
pip install omlt[keras]
# or
pip install omlt[torch]
# or
pip install omlt[linear-tree,keras-gpu]
```
Ofc any combination of optional dependencies is valid too. This allows
our users to install the dependencies specific to their use case. Note
that:

* I made `onnx` and `onnxruntime` a required dependency because from my
understanding it is almost always used
* I added an optinoal dependency set called `dev` which developers can
use to install all developer tools and all dependencies -- you need this
to run all the tests for example
* There is also `dev-gpu` which installs the GPU version of tensorflow
in case the developer has a GPU

The available optional dependencies are:

* `linear-tree`, installs the linear tree dependency
* `keras`, installs tensorflow and keras
* `keras-gpu`, installs tensorflow for the gpu and keras
* `torch`, installs torch and torch geometric
* `dev-tools` - this is not to be used directly but allows easy re-use
of dev tools in other optional dependencies, namely dev and dev-gpu
* `docs` - installs dependencies required to compile docs 
* `dev` - dependecies needed for developing the project, such tooling
* `dev-gpu` - same as dev but installed with gpu support

Our documentation probably needs to be updated to tell users they wanna
install omlt with some combination of `linear-tree`, `keras`,
`keras-gpu`, `torch` optional dependencies depending on what features of
the package they are using

# Quality checks with `ruff`, `mypy` and `doctest`

I've enabled `ruff`, `mypy` and `doctest`. Currently there are no
doctests, but its good to have it set up so that it runs in case any are
added in the future.

Both `ruff` and `mypy` are failing because there are a number of things
which need to fixed. For both `ruff` and `mypy` I have disabled some
checks which it would be good to enable eventually but are probably a
fair amount of work to fix -- these have comments in `pyproject.toml`.
The remaining failing checks are ones which I would reccomend fixing
ASAP. There's two approaches, merge now and fix these errors later. Or
keep a separate branch where these are incrementally fixed. Up to you to
decide what you prefer.

I told ruff to check for `google` style docstrings. I think these are
the best because they have good readbility and work the best with type
hints in my opinion.

# Using `just` instead of `tox`

https://github.com/casey/just is a simple command runner. It allows the
developers to define and re-use common operations, for example I can
define a `check` recipe and then run

```bash
just check
```

in my command line and it will run all the tests. The beauty of this is
that `just` is extremely simple. If you read the file its basically a
sequence of bash instructions for each recipe. This makes the `recipes`
really transparent, and easy to understand, and works as
code-as-documentation. Users can just read the recipe and run the
commands one by one to get the same effect without having `just`
installed. There is no magic which helps with debugging issues. It's
also language agnostic. `just` comes as a small stand-alone binary,
which makes it a very non-intrusive tool to have on your computer that
does not need any dependencies.

The downside is that it does not provide automatic management for Python
environments, which I belive tox does provide. The other side of this is
that we allow developers to use their favorite tools for managing venvs
rather than proscribing certain tools for this repo. (the difference
with `just` being that it is essentially optional tool and also serving
as documentation)

I may be overly opinionated on this one, so feel free to push back.

# Cleaning up `docs/conf.py`

I removed a bunch of the commented out code. This makes it easier to see
what the configuration is and also prevents the commented out options
from becoming out of date when a new release of sphinx is made.

# Moving `pull_request_template.md`

I moved this into the `.github` folder because it is GitHub
configuration. Very optional, but makes more sense to me.

# `readthedocs` automated action

this guide
https://docs.readthedocs.io/en/stable/guides/pull-requests.html shows
how to set it up. requires admin permissions on readthedocs -- can jump
on a call to help with this

# publishing with to `PYPI` with a git tag

for this an API key for PYPI needs to be created and added to the repos
secrets -- can jump on a call to help with this

# consider `_internal` package structure

One way to make it easier to manage private vs public code in a
repository is to create an `_internal` folder where all the code goes.
This way all code can be shared easily and moved between modules and its
by default private, so changes to internal code does not break users.
Public modules then just re-export code in the `_internal` submodules.
You can see an example of this structure here
https://github.com/lukasturcani/stk. Not a huge issue but I find it very
helpful for managing what things are actually exposed to users the
code-base grows.

**Legal Acknowledgement**\
By contributing to this software project, I agree my contributions are
submitted under the BSD license.
I represent I am authorized to make the contributions and grant the
license.
If my employer has rights to intellectual property that includes these
contributions,
I represent that I have received permission to make contributions and
grant the required license on behalf of that employer.

---------

Co-authored-by: Jeremy Sadler <[email protected]>
  • Loading branch information
lukasturcani and jezsadler authored Aug 20, 2024
1 parent e605638 commit d43643a
Show file tree
Hide file tree
Showing 81 changed files with 2,910 additions and 2,760 deletions.
28 changes: 0 additions & 28 deletions .coveragerc

This file was deleted.

File renamed without changes.
53 changes: 0 additions & 53 deletions .github/workflows/main.yml

This file was deleted.

23 changes: 23 additions & 0 deletions .github/workflows/publish_release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Publish release
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
jobs:
publish-release:
runs-on: ubuntu-22.04
env:
VERSION: ${{ github.ref_name }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: "pip"
- run: pip install -e '.[dev]'
- run: python -m build
- run:
twine upload
-u __token__
-p ${{ secrets.PYPI_API_TOKEN }}
dist/*
82 changes: 82 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
name: Tests
on:
push:
branches:
- main
pull_request:
workflow_dispatch:
jobs:
ruff:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: "pip"
- run: pip install '.[dev]'
- run: ruff check src/ tests/ docs/
mypy:
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12"]
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: "pip"
- run: pip install '.[dev]'
- run: mypy src/ tests/ docs/
ruff-format:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: "pip"
- run: pip install '.[dev]'
- run: ruff format --check src/ tests/ docs/
pytest:
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12"]
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: "pip"
- uses: actions/cache@v3
with:
path: ~/conda_pkgs_dir
key: ${{ runner.os }}-conda-${{ hashFiles('environment.yml') }}
- uses: conda-incubator/setup-miniconda@v3
with:
channel-priority: strict
environment-file: environment.yml
use-only-tar-bz2: true
- run: pip install '.[dev]'
- shell: bash -el {0}
run: pytest
- run: python -m coverage xml
- uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true
doctest:
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12"]
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: "pip"
- run: pip install '.[dev]'
- run: make -C docs doctest
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ share/python-wheels/
.installed.cfg
*.egg
MANIFEST
src/omlt/_version.py

# PyInstaller
# Usually these files are written by a python script from a template
Expand All @@ -50,6 +51,8 @@ coverage.xml
*.py,cover
.hypothesis/
.pytest_cache/
docs/notebooks/data/MNIST
docs/notebooks/neuralnet/*.keras

# Translations
*.mo
Expand All @@ -70,6 +73,7 @@ instance/

# Sphinx documentation
docs/_build/
docs/_autosummary

# PyBuilder
target/
Expand Down
9 changes: 5 additions & 4 deletions .readthedocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ build:

tools:

python: "3.8"
python: "3.12"

# You can also specify other tool versions:

Expand Down Expand Up @@ -58,7 +58,8 @@ sphinx:
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html

python:

install:

- requirements: docs/requirements.txt
- method: pip
path: .
extra_requirements:
- docs
10 changes: 0 additions & 10 deletions Makefile

This file was deleted.

16 changes: 8 additions & 8 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -142,14 +142,14 @@ Example
Development
===========

OMLT uses `tox` to manage development tasks:
OMLT uses `just <https://github.com/casey/just>`_ to manage development tasks:

* `tox -av` to list available tasks
* `tox` to run tests
* `tox -e lint` to check formatting and code styles
* `tox -e format` to automatically format files
* `tox -e docs` to build the documentation
* `tox -e publish` to publish the package to PyPi
* ``just`` to list available tasks
* ``just check`` to run all checks
* ``just fix`` to apply any auto-fixes
* ``just dev`` to install development dependencies in your current Python environment
* ``just dev-gpu`` same as ``dev`` but with GPU support
* ``just docs`` to build the documentation

Contributors
============
Expand Down Expand Up @@ -224,4 +224,4 @@ Contributors

.. _zshiqiang: https://github.com/zshiqiang
.. |zshiqiang| image:: https://avatars.githubusercontent.com/u/91337036?v=4
:width: 80px
:width: 80px
15 changes: 3 additions & 12 deletions docs/Makefile
Original file line number Diff line number Diff line change
@@ -1,27 +1,18 @@
# Makefile for Sphinx documentation
# Minimal makefile for Sphinx documentation
#

# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXOPTS ?= -W --keep-going
SPHINXBUILD ?= sphinx-build
SOURCEDIR = .
BUILDDIR = _build
#AUTODOCDIR = api

# User-friendly check for sphinx-build
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $?), 1)
$(error "The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/")
endif

.PHONY: help clean Makefile

# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

clean:
rm -rf $(BUILDDIR)/* #$(AUTODOCDIR)
.PHONY: help Makefile

# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
Expand Down
Loading

0 comments on commit d43643a

Please sign in to comment.