Skip to content

Commit

Permalink
Merge pull request #1 from jarq6c/docker
Browse files Browse the repository at this point in the history
Update package template to use docker
  • Loading branch information
jarq6c authored Feb 23, 2023
2 parents e780ab8 + 2176a34 commit a6ba15c
Show file tree
Hide file tree
Showing 17 changed files with 176 additions and 90 deletions.
4 changes: 4 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
**/*.egg-info
**/__pycache__
env/
venv/
5 changes: 0 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ venv/
ENV/
env.bak/
venv.bak/
miniconda3/

# Spyder project settings
.spyderproject
Expand All @@ -128,7 +127,3 @@ dmypy.json

# Pyre type checker
.pyre/

# Miniconda
Miniconda3*
sha256sum.txt
19 changes: 19 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# syntax=docker/dockerfile:1
FROM python:3.10-slim AS builder
RUN python3 -m pip install -U pip wheel setuptools
WORKDIR /project/install
COPY requirements.py requirements.py
COPY setup.cfg setup.cfg
RUN python3 requirements.py > requirements.txt
RUN python3 -m pip install -r requirements.txt

FROM builder
COPY EXCLUDE EXCLUDE
COPY LICENSE LICENSE
COPY MANIFEST.in MANIFEST.in
COPY pyproject.toml pyproject.toml
COPY README.md README.md
COPY src src
COPY tests tests
RUN python3 -m pip install .[develop]
ENTRYPOINT [ "/usr/local/bin/python3", "-m", "build"]
5 changes: 2 additions & 3 deletions EXCLUDE
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
*__pycache__*
*.py[cod]
*$py.class
miniconda3/*
sha256sum.txt
Miniconda3-py*
*.pytest_cache*
env/*
venv/*
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2021 Jason A. Regina
Copyright (c) 2023 Author Name

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
44 changes: 15 additions & 29 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,34 +1,20 @@
REPOSITORY=https://repo.anaconda.com/miniconda
INSTALLER=Miniconda3-py38_4.9.2-Linux-x86_64.sh
HASH=1314b90489f154602fd794accfc90446111514a5a72fe1f71ab83e07de9504a7
HASHFILE=sha256sum.txt
PYENV=miniconda3
PYTHON=$(PYENV)/bin/python3
TAG=my-tag
APP=my-cli
ARGUMENTS=-c 5 -r 7

.PHONY: develop tests build checksum clean
.PHONY: build setup run tests

develop: $(PYENV)/bin/activate
$(PYTHON) -m pip install -e .[develop]
build: tests
docker run --name $(TAG)-builder $(TAG)
docker cp $(TAG)-builder:/project/install/dist $(PWD)/dist
docker stop $(TAG)-builder
docker rm $(TAG)-builder

tests: develop
$(PYTHON) -m pytest -s
setup:
docker build -t $(TAG) .

build: $(PYENV)/bin/activate
$(PYTHON) -m build
run: setup
docker run --entrypoint "/usr/local/bin/$(APP)" --rm $(TAG) $(ARGUMENTS)

$(PYENV)/bin/activate: checksum
test -d $(PYENV) || bash ./$(INSTALLER) -b -p $(PYENV)
$(PYTHON) -m pip install -U pip wheel setuptools build
touch $(PYENV)/bin/activate

checksum: $(INSTALLER) $(HASHFILE)
sha256sum -c $(HASHFILE)

$(INSTALLER):
wget $(REPOSITORY)/$(INSTALLER)

$(HASHFILE):
echo "$(HASH) $(INSTALLER)" > $(HASHFILE)

clean:
rm -rf dist/ $(PYENV) $(HASHFILE) $(INSTALLER)
tests: setup
docker run --entrypoint "/usr/local/bin/pytest" --rm $(TAG)
34 changes: 23 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,44 @@
# Generic Python Project

This template can be used as a starting point for building distributable Python packages. It includes a Makefile with targets to build a wheel and a reproducible Python virtual environment based on miniconda. To learn more about packaging in Python see the [Python Packaging User Guide](https://packaging.python.org/).
This template can be used as a starting point for building distributable Python packages. It includes a Makefile with targets to setup, run, test, and build a final installable PyPI compatible package. To learn more about packaging in Python see the [Python Packaging User Guide](https://packaging.python.org/).

## Build

```bash
$ make
```
Running `make` in this directory will build the default target "build". The chain of dependencies for the default target includes targets that retrieve a miniconda installation script for Linux and creates a stand-alone miniconda environment with the required dependencies. The name of the miniconda environment is `miniconda3` and can be activated as normal using `source miniconda3/bin/activate` and deactivated using `conda deactivate`. The build target runs `python -m build` which collects the required files and packages in `src` and generates an install wheel under `dist`.

To start from scratch run clean:
```bash
$ make clean
```
Running `make` in this directory will build the default target "build". The chain of dependencies for the default target includes targets that build a docker image with development dependencies. This target will also run `pytest` and `build`, and copy the resulting `dist` directory with `.whl` and `.tar.gz` packages into the currect directory.

## Installation

After running build, you can install the default package by running

```
miniconda3/bin/python -m pip install dist/hello-0.1.0-py3-none-any.whl
python -m pip install dist/my_package-0.1.0-py3-none-any.whl
```

...or, assuming you have an API token and a valid [`.pypirc` file](https://packaging.python.org/en/latest/specifications/pypirc/) you can upload and install your package from PyPI with

```bash
python3 -m twine upload dist/*
python3 -m pip install my_package
```

## Testing
See [Packaging Python Projects](https://packaging.python.org/en/latest/tutorials/packaging-projects/) for more details on uploading your package.

An example test is in `tests/test_hello.py`. This test uses `pytest` to test the `greet` function. You can read more about using `pytest` at the [PyTest Documentation](https://docs.pytest.org/). To run all tests just use:
## Other targets

```bash
$ make setup
```
miniconda3/bin/python -m pytest
This target builds the docker image and is required by all other targets.

```bash
$ make run
```
This target runs the CLI application from a docker container indicated in `setup.cfg` with the arguments specified by `ARGUMENTS` in the `Makefile`. This target is mainly a convenience and reference for users. CLI arguments are more easily changed by running this underlying command directly from the command prompt.

```bash
$ make tests
```
This target runs `pytest` on the code in the image created by "setup" in a docker container.
38 changes: 23 additions & 15 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,35 +1,37 @@
[metadata]
name = hello
version = 0.1.0
author = Your Name
author_email = your.name@noaa.gov
description = Generic template for data-driven projects using Python.
name = my_package
version = attr: my_package._version.__version__
author = Author Name
author_email = author.name@email.com
description = My package description.
long_description = file: README.md
long_description_content_type = text/markdown; charset=UTF-8
license = USDOC
license = MIT
license_files =
LICENSE
url = https://github.com/NOAA-OWP/
url = https://github.com/my_github/my_package
project_urls =
Documentation = https://github.com/NOAA-OWP/
Source = https://github.com/NOAA-OWP/
Tracker = https://github.com/NOAA-OWP/
Documentation = https://my-github.github.io/me/my_package.html
Source = https://github.com/my_github/my_package
Tracker = https://github.com/my_github/my_package/issues
classifiers =
Development Status :: 1 - Planning
Development Status :: 3 - Alpha
Intended Audience :: Education
Intended Audience :: Science/Research
License :: Free To Use But Restricted
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Topic :: Scientific/Engineering :: Hydrology
Operating System :: OS Independent
Private :: Do not Upload
License :: OSI Approved :: MIT License

[options]
packages = find:
packages = find_namespace:
package_dir =
=src
install_requires =
numpy
pandas
click
python_requires = >=3.8
include_package_data = True

Expand All @@ -39,3 +41,9 @@ where = src
[options.extras_require]
develop =
pytest
build

[options.entry_points]
console_scripts =
my-cli = my_package.cli:run

Empty file removed src/hello/__init__.py
Empty file.
17 changes: 0 additions & 17 deletions src/hello/hello.py

This file was deleted.

3 changes: 3 additions & 0 deletions src/my_package/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# removing __version__ import will cause build to fail.
# see: https://github.com/pypa/setuptools/issues/1724#issuecomment-627241822
from ._version import __version__
1 change: 1 addition & 0 deletions src/my_package/_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = "0.1.0"
28 changes: 28 additions & 0 deletions src/my_package/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import click
import pandas as pd
from my_package.my_module import make_dataframe
from my_package._version import __version__

@click.command()
@click.option("-c", "--ncols", "ncols", nargs=1, type=int, help="Number of columns", default=3)
@click.option("-r", "--nrows", "nrows", nargs=1, type=int, help="Number of rows", default=4)
def run(
ncols: int,
nrows: int
) -> None:
"""Generate a random dataframe and print to screen.
Example:
my-cli -c 3 -r 6
"""
# Get a dataframe
df = make_dataframe(ncols=ncols, nrows=nrows)

# Print
print(f"my_package {__version__}")
with pd.option_context("display.precision", 2):
print(df)

if __name__ == "__main__":
run()
24 changes: 24 additions & 0 deletions src/my_package/my_module.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""Generic example module"""
from string import ascii_uppercase
import numpy as np
import pandas as pd

def make_dataframe(ncols: int = 4, nrows: int = 5) -> pd.DataFrame:
"""
Generate a pandas.DataFrame of random data with ncols columns and
nrows index length.
Parameters
----------
ncols: int, optional, default 4
Number of columns for resulting dataframe.
nrows: int, optional, default 5
Number of rows of resulting dataframe.
Returns
-------
pandas.DataFrame with ncols columns and index length nrows.
"""
# Return dataframe
return pd.DataFrame({c: np.random.random(nrows) for c in ascii_uppercase[:ncols]})
9 changes: 0 additions & 9 deletions tests/test_hello.py

This file was deleted.

17 changes: 17 additions & 0 deletions tests/test_my_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import pytest
from click.testing import CliRunner
from my_package.cli import run
from my_package._version import __version__

@pytest.fixture
def runner_result():
runner = CliRunner()
return runner.invoke(run)

def test_hello_world(runner_result):
# Check exit code
assert runner_result.exit_code == 0

# Check first line of output
first_line = runner_result.output.split("\n")[0]
assert first_line == f"my_package {__version__}"
16 changes: 16 additions & 0 deletions tests/test_my_package.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import pytest
import my_package
from my_package import my_module

def test_package_version():
# Check version
assert my_package.__version__ == "0.1.0"

@pytest.fixture
def default_dataframe():
return my_module.make_dataframe()

def test_default_dataframe(default_dataframe):
# Test defaults
assert len(default_dataframe.columns) == 4
assert len(default_dataframe.index) == 5

0 comments on commit a6ba15c

Please sign in to comment.