Skip to content

Commit d1b809c

Browse files
authored
Merge pull request #10 from biosustain/cdci update
🚧 start with GitHub Actions to ensure tests pass before larger refactor
2 parents 284d084 + 402a512 commit d1b809c

File tree

15 files changed

+398
-127
lines changed

15 files changed

+398
-127
lines changed

.github/workflows/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Workflows
2+
3+
## cicd
4+
5+
Basic continuous integration and deployment (CI/CD) workflow for Python packages.
6+
7+
- checks formatting (black, isort)
8+
- checks linting (ruff)
9+
- run unit tests (pytest)
10+
- optional: add c extensions to a package
11+
- if all checks pass, build and deploy to PyPI if `tag` triggered the workflow

.github/workflows/cicd.yml

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# This workflow will install Python dependencies, run tests and lint with a single version of Python
2+
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
3+
4+
name: CI/CD
5+
6+
on:
7+
push:
8+
pull_request:
9+
branches: ["main"]
10+
schedule:
11+
- cron: "0 2 * * 3"
12+
13+
permissions:
14+
contents: read
15+
16+
jobs:
17+
format:
18+
runs-on: ubuntu-latest
19+
steps:
20+
- uses: actions/checkout@v4
21+
- uses: psf/black@stable
22+
- uses: isort/isort-action@v1
23+
lint:
24+
name: Lint with ruff
25+
runs-on: ubuntu-latest
26+
steps:
27+
- uses: actions/checkout@v4
28+
29+
- uses: actions/setup-python@v5
30+
with:
31+
python-version: "3.12"
32+
- name: Install ruff
33+
run: |
34+
pip install ruff
35+
- name: Lint with ruff
36+
run: |
37+
# stop the build if there are Python syntax errors or undefined names
38+
ruff check .
39+
test:
40+
name: Test
41+
runs-on: macos-latest
42+
strategy:
43+
matrix:
44+
python-version: ["3.11", "3.12", "3.13"]
45+
steps:
46+
- uses: actions/checkout@v4
47+
- name: Set up Python ${{ matrix.python-version }}
48+
uses: actions/setup-python@v5
49+
with:
50+
python-version: ${{ matrix.python-version }}
51+
cache: "pip" # caching pip dependencies
52+
cache-dependency-path: "**/pyproject.toml"
53+
- name: Install dependencies
54+
run: |
55+
python -m pip install --upgrade pip
56+
pip install pytest
57+
pip install -e .
58+
- name: Run tests
59+
run: python -m pytest tests
60+
61+
build_source_dist:
62+
name: Build source distribution
63+
if: startsWith(github.ref, 'refs/heads/main') || startsWith(github.ref, 'refs/tags')
64+
runs-on: ubuntu-latest
65+
steps:
66+
- uses: actions/checkout@v4
67+
68+
- uses: actions/setup-python@v5
69+
with:
70+
python-version: "3.10"
71+
72+
- name: Install build
73+
run: python -m pip install build
74+
75+
- name: Run build
76+
run: python -m build --sdist
77+
78+
- uses: actions/upload-artifact@v4
79+
with:
80+
path: ./dist/*.tar.gz
81+
# Needed in case of building packages with external binaries (e.g. Cython, RUst-extensions, etc.)
82+
# build_wheels:
83+
# name: Build wheels on ${{ matrix.os }}
84+
# if: startsWith(github.ref, 'refs/heads/main') || startsWith(github.ref, 'refs/tags')
85+
# runs-on: ${{ matrix.os }}
86+
# strategy:
87+
# matrix:
88+
# os: [ubuntu-20.04, windows-2019, macOS-10.15]
89+
90+
# steps:
91+
# - uses: actions/checkout@v4
92+
93+
# - uses: actions/setup-python@v5
94+
# with:
95+
# python-version: "3.10"
96+
97+
# - name: Install cibuildwheel
98+
# run: python -m pip install cibuildwheel==2.3.1
99+
100+
# - name: Build wheels
101+
# run: python -m cibuildwheel --output-dir wheels
102+
103+
# - uses: actions/upload-artifact@v4
104+
# with:
105+
# path: ./wheels/*.whl
106+
107+
publish:
108+
name: Publish package
109+
if: startsWith(github.ref, 'refs/tags')
110+
permissions:
111+
id-token: write
112+
needs:
113+
- format
114+
- lint
115+
- test
116+
- build_source_dist
117+
# - build_wheels
118+
runs-on: ubuntu-latest
119+
120+
steps:
121+
- uses: actions/download-artifact@v4
122+
with:
123+
name: artifact
124+
path: ./dist
125+
# register PyPI integration:
126+
# https://packaging.python.org/en/latest/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/
127+
- uses: pypa/gh-action-pypi-publish@release/v1
128+
with:
129+
# remove repository key to set the default to pypi (not test.pypi.org)
130+
repository-url: https://test.pypi.org/legacy/

.gitignore

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,62 @@ nosetests.xml
4040
.vscode
4141

4242
# Test output
43-
test.*.pdf
43+
test.*.pdf
44+
45+
46+
# find more examples here:
47+
# https://github.com/github/gitignore/blob/main/Python.gitignore
48+
# Binaries (object files) produced by a compiler
49+
*.so
50+
*.o
51+
52+
# Python/Jupyter uses these cache directories
53+
__pycache__
54+
.ipynb_checkpoints
55+
56+
# Produced when measuring code coverage
57+
.coverage
58+
59+
# These files are produced during the Python build process
60+
build/
61+
.eggs
62+
dist/
63+
**.egg-info
64+
65+
# Sphinx documenter creates these
66+
_build
67+
_static
68+
_templates
69+
/docs/reference
70+
/docs/jupyter_execute
71+
72+
# MacOS automatically creates these files
73+
.DS_Store
74+
75+
# VSCode may create a config file with this name
76+
**.vscode
77+
78+
# Ruff stuff:
79+
.ruff_cache/
80+
81+
# Environments
82+
.env
83+
.envrc
84+
.venv
85+
env/
86+
venv/
87+
88+
# Unit test / coverage reports
89+
htmlcov/
90+
.tox/
91+
.nox/
92+
.coverage
93+
.coverage.*
94+
.cache
95+
nosetests.xml
96+
coverage.xml
97+
*.cover
98+
*.py.cover
99+
.hypothesis/
100+
.pytest_cache/
101+
cover/

.travis.yml

Lines changed: 0 additions & 13 deletions
This file was deleted.

croissance/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
import pandas
22

33
import croissance.figures.plot
4-
54
from croissance.estimation import (
65
AnnotatedGrowthCurve,
76
GrowthEstimationParameters,
87
estimate_growth,
98
)
109
from croissance.estimation.util import normalize_time_unit
1110

12-
1311
__all__ = [
1412
"plot_processed_curve",
1513
"process_curve",

croissance/estimation/__init__.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import logging
2-
32
from collections import namedtuple
43
from operator import attrgetter
54

@@ -10,7 +9,7 @@
109
from croissance.estimation.ranking import rank_phases
1110
from croissance.estimation.regression import fit_exponential
1211
from croissance.estimation.smoothing.segments import segment_spline_smoothing
13-
from croissance.estimation.util import savitzky_golay, points_per_hour
12+
from croissance.estimation.util import points_per_hour, savitzky_golay
1413

1514

1615
class RawGrowthPhase(namedtuple("RawGrowthPhase", ("start", "end"))):
@@ -78,11 +77,14 @@ def __init__(self):
7877
}
7978

8079

80+
growth_estimation_defaults = GrowthEstimationParameters()
81+
82+
8183
def estimate_growth(
8284
curve: pandas.Series,
8385
*,
84-
params=GrowthEstimationParameters(),
85-
name: str = "untitled curve"
86+
params=growth_estimation_defaults,
87+
name: str = "untitled curve",
8688
) -> AnnotatedGrowthCurve:
8789
log = logging.getLogger(__name__)
8890
series = curve.dropna()

croissance/estimation/regression.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ def exponential_constrain_n0(x, a, b):
3131
series.values,
3232
p0=p0,
3333
maxfev=10000,
34-
bounds=([0.0, 0.0, 0.0], numpy.inf)
35-
if n0 is None
36-
else ([0.0, 0.0], numpy.inf),
34+
bounds=(
35+
([0.0, 0.0, 0.0], numpy.inf) if n0 is None else ([0.0, 0.0], numpy.inf)
36+
),
3737
)
3838

3939
if n0 is not None:
@@ -66,9 +66,9 @@ def exponential_constrain_n0(x, a, b):
6666
series.values,
6767
p0=p0,
6868
maxfev=10000,
69-
bounds=([0.0, 0.0, 0.0], numpy.inf)
70-
if n0 is None
71-
else ([0.0, 0.0], numpy.inf),
69+
bounds=(
70+
([0.0, 0.0, 0.0], numpy.inf) if n0 is None else ([0.0, 0.0], numpy.inf)
71+
),
7272
)
7373

7474
if n0 is not None:

croissance/estimation/smoothing/segments.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import numpy
22
import pandas
3-
4-
from scipy.signal import detrend
53
from scipy.interpolate import InterpolatedUnivariateSpline
4+
from scipy.signal import detrend
65

76

87
def segment_by_std_dev(series, increment=2, maximum=20):

croissance/figures/plot.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def _do_plot_processed_curve(curve, axis, yscale):
5353

5454
a = 1 / numpy.exp(phase.intercept * phase.slope)
5555

56-
def gf(x):
56+
def gf(x, phase=phase, a=a):
5757
return a * numpy.exp(phase.slope * x) + phase.n0
5858

5959
phase_series = curve.series[phase.start : phase.end]

croissance/figures/writer.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import matplotlib.pyplot as plt
2-
32
from matplotlib.backends.backend_pdf import PdfPages
43

54
from croissance.estimation import AnnotatedGrowthCurve

0 commit comments

Comments
 (0)