Skip to content

Commit

Permalink
Add makefile to simplify common commands
Browse files Browse the repository at this point in the history
Such as linting, testing, type-checking etc. Also reformat the code
to pass the linting and type-checking requirements.
  • Loading branch information
fjclark committed Sep 17, 2024
1 parent 0631f08 commit 5824cb5
Show file tree
Hide file tree
Showing 32 changed files with 411 additions and 309 deletions.
3 changes: 1 addition & 2 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# How to contribute

We welcome contributions from external contributors, and this document
describes how to merge code changes into this EnsEquil.
We welcome contributions from external contributors, and this document describes how to merge code changes into this repository.

## Getting Started

Expand Down
2 changes: 1 addition & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ Notable points that this PR has either accomplished or will accomplish.
- [ ] Question1

## Status
- [ ] Ready to go
- [ ] Ready to go
4 changes: 2 additions & 2 deletions .github/workflows/CI.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ jobs:
- uses: mamba-org/setup-micromamba@v1
with:
environment-file: devtools/conda-envs/test_env.yaml
environment-name: test
environment-file: devtools/conda-envs/test.yaml
environment-name: red
create-args: >- # beware the >- instead of |, we don't split on newlines but on spaces
python=${{ matrix.python-version }}
Expand Down
30 changes: 30 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.2.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer

- repo: local
hooks:
- id: ruff
name: Autoformat python code
language: system
entry: bash
args: [-c, "make format"]

- repo: local
hooks:
- id: ruff
name: Lint python code
language: system
entry: bash
args: [-c, "make lint"]

- repo: local
hooks:
- id: mypy
name: Type check python code
language: system
entry: bash
args: [-c, "make type-check"]
2 changes: 1 addition & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
include CODE_OF_CONDUCT.md

global-exclude *.py[cod] __pycache__ *.so
global-exclude *.py[cod] __pycache__ *.so
36 changes: 36 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
PACKAGE_NAME := red
PACKAGE_DIR := red

CONDA_ENV_RUN = conda run --no-capture-output --name $(PACKAGE_NAME)

TEST_ARGS := -v --cov=$(PACKAGE_NAME) --cov-report=term --cov-report=xml --junitxml=unit.xml --color=yes

.PHONY: env lint format test type-check docs-build docs-deploy

env:
mamba create --name $(PACKAGE_NAME)
mamba env update --name $(PACKAGE_NAME) --file devtools/conda-envs/test.yaml
$(CONDA_ENV_RUN) pip install --no-deps -e .
$(CONDA_ENV_RUN) pre-commit install || true

lint:
$(CONDA_ENV_RUN) ruff check $(PACKAGE_DIR)

format:
$(CONDA_ENV_RUN) ruff format $(PACKAGE_DIR)
$(CONDA_ENV_RUN) ruff check --fix --select I $(PACKAGE_DIR)

test:
$(CONDA_ENV_RUN) pytest -v $(TEST_ARGS) $(PACKAGE_DIR)/tests/

type-check:
$(CONDA_ENV_RUN) mypy --follow-imports=silent --ignore-missing-imports --strict $(PACKAGE_DIR)

docs-build:
$(CONDA_ENV_RUN) mkdocs build

docs-deploy:
ifndef VERSION
$(error VERSION is not set)
endif
$(CONDA_ENV_RUN) mike deploy --push --update-aliases $(VERSION)
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ Robust Equilibration Detection
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)

A Python package for detecting equilibration in timeseries data where an initial transient is followed by a stationary distribution. Two main methods are implemented, which differ in the way they account for autocorrelation:
A Python package for detecting equilibration in timeseries data where an initial transient is followed by a stationary distribution. Two main methods are implemented, which differ in the way they account for autocorrelation:

- `detect_equilibration_init_seq`: This uses the initial sequence methods of Geyer ([Geyer, 1992](https://www.jstor.org/stable/2246094)) to determine the truncation point of the sum of autocovariances.
- `detect_equilibration_window`: This uses window methods (see [Geyer](https://www.jstor.org/stable/2246094) again) when calculating the
autocorrelation.
- `detect_equilibration_window`: This uses window methods (see [Geyer](https://www.jstor.org/stable/2246094) again) when calculating the
autocorrelation.

For both, the equilibration point can be determined either according to the minimum of the squared standard error (the default), or the maximum effective sample size, by specifying `method="min_sse"` or `method="max_ess"`.

Expand All @@ -40,7 +40,7 @@ my_timeseries = ...
# Detect equilibration based on the minimum squared standard error
# using Geyer's initial convex sequence method to account for
# autocorrelation. idx is the index of the first sample after
# equilibration, g is the statistical inefficiency of the equilibrated
# equilibration, g is the statistical inefficiency of the equilibrated
# sample, and ess is the effective sample size of the equilibrated sample.
idx, g, ess = red.detect_equilibration_init_seq(my_timeseries, method="min_sse", plot=True)

Expand All @@ -58,6 +58,6 @@ Copyright (c) 2023, Finlay Clark


#### Acknowledgements
Project based on the

Project based on the
[Computational Molecular Science Python Cookiecutter](https://github.com/molssi/cookiecutter-cms) version 1.1.
16 changes: 8 additions & 8 deletions devtools/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# Development, testing, and deployment tools

This directory contains a collection of tools for running Continuous Integration (CI) tests,
This directory contains a collection of tools for running Continuous Integration (CI) tests,
conda installation, and other development tools not directly related to the coding process.


## Manifest

### Continuous Integration

You should test your code, but do not feel compelled to use these specific programs. You also may not need Unix and
You should test your code, but do not feel compelled to use these specific programs. You also may not need Unix and
Windows testing if you only plan to deploy on specific platforms. These are just to help you get started.

### Conda Environment:
Expand All @@ -17,7 +17,7 @@ This directory contains the files to setup the Conda environment for testing pur

* `conda-envs`: directory containing the YAML file(s) which fully describe Conda Environments, their dependencies, and those dependency provenance's
* `test_env.yaml`: Simple test environment file with base dependencies. Channels are not specified here and therefore respect global Conda configuration

### Additional Scripts:

This directory contains OS agnostic helper scripts which don't fall in any of the previous categories
Expand All @@ -40,17 +40,17 @@ This directory contains OS agnostic helper scripts which don't fall in any of th
- [ ] Make sure there is an/are issue(s) opened for your specific update
- [ ] Create the PR, referencing the issue
- [ ] Debug the PR as needed until tests pass
- [ ] Tag the final, debugged version
- [ ] Tag the final, debugged version
* `git tag -a X.Y.Z [latest pushed commit] && git push --follow-tags`
- [ ] Get the PR merged in

## Versioneer Auto-version
[Versioneer](https://github.com/warner/python-versioneer) will automatically infer what version
is installed by looking at the `git` tags and how many commits ahead this version is. The format follows
[Versioneer](https://github.com/warner/python-versioneer) will automatically infer what version
is installed by looking at the `git` tags and how many commits ahead this version is. The format follows
[PEP 440](https://www.python.org/dev/peps/pep-0440/) and has the regular expression of:
```regexp
\d+.\d+.\d+(?\+\d+-[a-z0-9]+)
```
If the version of this commit is the same as a `git` tag, the installed version is the same as the tag,
e.g. `red-0.1.2`, otherwise it will be appended with `+X` where `X` is the number of commits
If the version of this commit is the same as a `git` tag, the installed version is the same as the tag,
e.g. `red-0.1.2`, otherwise it will be appended with `+X` where `X` is the number of commits
ahead from the last tag, and then `-YYYYYY` where the `Y`'s are replaced with the `git` commit hash.
15 changes: 15 additions & 0 deletions devtools/conda-envs/base.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: red

channels:
- conda-forge
- defaults

dependencies:
# Base depends
- python
- pip
- numpy
- numba
- scipy
- matplotlib
- statsmodels
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
name: test
channels:
name: red

channels:
- conda-forge

- defaults

dependencies:
# Base depends
- python
Expand All @@ -14,8 +14,12 @@ dependencies:
- matplotlib
- statsmodels

# Testing
# Testing / Development
- pytest
- pytest-cov
- pytest-env
- codecov
- ruff
- pre-commit
- mypy
- types-PyYAML
76 changes: 49 additions & 27 deletions devtools/scripts/create_conda_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
import subprocess as sp
from tempfile import TemporaryDirectory
from contextlib import contextmanager

# YAML imports
try:
import yaml # PyYAML

loader = yaml.safe_load
except ImportError:
try:
Expand All @@ -17,19 +19,31 @@
try:
# Load Ruamel YAML from the base conda environment
from importlib import util as import_util
CONDA_BIN = os.path.dirname(os.environ['CONDA_EXE'])
ruamel_yaml_path = glob.glob(os.path.join(CONDA_BIN, '..',
'lib', 'python*.*', 'site-packages',
'ruamel_yaml', '__init__.py'))[0]
# Based on importlib example, but only needs to load_module since its the whole package, not just
# a module
spec = import_util.spec_from_file_location('ruamel_yaml', ruamel_yaml_path)

CONDA_BIN = os.path.dirname(os.environ["CONDA_EXE"])
ruamel_yaml_path = glob.glob(
os.path.join(
CONDA_BIN,
"..",
"lib",
"python*.*",
"site-packages",
"ruamel_yaml",
"__init__.py",
)
)[0]
# Based on importlib example, but only needs to load_module since its the whole
# package, not just a module
spec = import_util.spec_from_file_location("ruamel_yaml", ruamel_yaml_path)
yaml = spec.loader.load_module()
except (KeyError, ImportError, IndexError):
raise ImportError("No YAML parser could be found in this or the conda environment. "
"Could not find PyYAML or Ruamel YAML in the current environment, "
"AND could not find Ruamel YAML in the base conda environment through CONDA_EXE path. "
"Environment not created!")
raise ImportError( # noqa: B904
"No YAML parser could be found in this or the conda environment. "
"Could not find PyYAML or Ruamel YAML in the current environment, "
"AND could not find Ruamel YAML in the base conda environment through "
"CONDA_EXE path. "
"Environment not created!"
)
loader = yaml.YAML(typ="safe").load # typ="safe" avoids odd typing on output


Expand All @@ -46,13 +60,14 @@ def temp_cd():


# Args
parser = argparse.ArgumentParser(description='Creates a conda environment from file for a given Python version.')
parser.add_argument('-n', '--name', type=str,
help='The name of the created Python environment')
parser.add_argument('-p', '--python', type=str,
help='The version of the created Python environment')
parser.add_argument('conda_file',
help='The file for the created Python environment')
parser = argparse.ArgumentParser(
description="Creates a conda environment from file for a given Python version."
)
parser.add_argument("-n", "--name", type=str, help="The name of the created Python environment")
parser.add_argument(
"-p", "--python", type=str, help="The version of the created Python environment"
)
parser.add_argument("conda_file", help="The file for the created Python environment")

args = parser.parse_args()

Expand All @@ -63,24 +78,31 @@ def temp_cd():
python_replacement_string = "python {}*".format(args.python)

try:
for dep_index, dep_value in enumerate(yaml_script['dependencies']):
if re.match('python([ ><=*]+[0-9.*]*)?$', dep_value): # Match explicitly 'python' and its formats
yaml_script['dependencies'].pop(dep_index)
break # Making the assumption there is only one Python entry, also avoids need to enumerate in reverse
for dep_index, dep_value in enumerate(yaml_script["dependencies"]):
if re.match(
"python([ ><=*]+[0-9.*]*)?$", dep_value
): # Match explicitly 'python' and its formats
yaml_script["dependencies"].pop(dep_index)
# Making the assumption there is only one Python entry, also avoids
# need to enumerate in reverse
break
except (KeyError, TypeError):
# Case of no dependencies key, or dependencies: None
yaml_script['dependencies'] = []
yaml_script["dependencies"] = []
finally:
# Ensure the python version is added in. Even if the code does not need it, we assume the env does
yaml_script['dependencies'].insert(0, python_replacement_string)
# Ensure the python version is added in. Even if the code does not
# need it, we assume the env does
yaml_script["dependencies"].insert(0, python_replacement_string)

# Figure out conda path
if "CONDA_EXE" in os.environ:
conda_path = os.environ["CONDA_EXE"]
else:
conda_path = shutil.which("conda")
if conda_path is None:
raise RuntimeError("Could not find a conda binary in CONDA_EXE variable or in executable search path")
raise RuntimeError(
"Could not find a conda binary in CONDA_EXE variable or in executable search path"
)

print("CONDA ENV NAME {}".format(args.name))
print("PYTHON VERSION {}".format(args.python))
Expand All @@ -90,6 +112,6 @@ def temp_cd():
# Write to a temp directory which will always be cleaned up
with temp_cd():
temp_file_name = "temp_script.yaml"
with open(temp_file_name, 'w') as f:
with open(temp_file_name, "w") as f:
f.write(yaml.dump(yaml_script))
sp.call("{} env create -n {} -f {}".format(conda_path, args.name, temp_file_name), shell=True)
2 changes: 1 addition & 1 deletion docs/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ help:
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
5 changes: 2 additions & 3 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ To compile the docs, first ensure that Sphinx and the ReadTheDocs theme are inst


```bash
conda install sphinx sphinx_rtd_theme
conda install sphinx sphinx_rtd_theme
```


Expand All @@ -14,11 +14,10 @@ Once installed, you can use the `Makefile` in this directory to compile static H
make html
```

The compiled docs will be in the `_build` directory and can be viewed by opening `index.html` (which may itself
The compiled docs will be in the `_build` directory and can be viewed by opening `index.html` (which may itself
be inside a directory called `html/` depending on what version of Sphinx is installed).


A configuration file for [Read The Docs](https://readthedocs.org/) (readthedocs.yaml) is included in the top level of the repository. To use Read the Docs to host your documentation, go to https://readthedocs.org/ and connect this repository. You may need to change your default branch to `main` under Advanced Settings for the project.

If you would like to use Read The Docs with `autodoc` (included automatically) and your package has dependencies, you will need to include those dependencies in your documentation yaml file (`docs/requirements.yaml`).

4 changes: 2 additions & 2 deletions docs/_static/README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Static Doc Directory

Add any paths that contain custom static files (such as style sheets) here,
relative to the `conf.py` file's directory.
relative to the `conf.py` file's directory.
They are copied after the builtin static files,
so a file named "default.css" will overwrite the builtin "default.css".

The path to this folder is set in the Sphinx `conf.py` file in the line:
The path to this folder is set in the Sphinx `conf.py` file in the line:
```python
templates_path = ['_static']
```
Expand Down
Loading

0 comments on commit 5824cb5

Please sign in to comment.