From 77eba0bae518e49f41495641c25f5ef6c5e8e3b1 Mon Sep 17 00:00:00 2001 From: Will Handley Date: Tue, 30 Apr 2024 11:25:37 +0100 Subject: [PATCH] Python project scaffolding (#1) * python project scaffolding * Updated documentation --- .github/CODE_OF_CONDUCT.md | 76 +++++++ .github/CONTRIBUTING.md | 24 +++ .github/ISSUE_TEMPLATE/bug_report.md | 21 ++ .github/ISSUE_TEMPLATE/feature_request.md | 18 ++ .github/PULL_REQUEST_TEMPLATE.md | 15 ++ .github/workflows/build.yaml | 169 +++++++++++++++ .github/workflows/code_style.yaml | 49 +++++ .github/workflows/pr_checks.yaml | 58 +++++ .github/workflows/unittests.yaml | 29 +++ .gitignore | 11 + .pre-commit-config.yaml | 23 ++ .readthedocs.yml | 25 +++ LICENSE | 21 ++ bin/bump_version.py | 41 ++++ bin/check_version.py | 32 +++ bin/dependencies.py | 7 + bin/gen_PKGBUILD.py | 49 +++++ bin/run_tests | 13 ++ bin/utils.py | 40 ++++ docs/Makefile | 19 ++ docs/make.bat | 35 +++ docs/source/conf.py | 246 ++++++++++++++++++++++ docs/source/index.rst | 10 + docs/source/modules.rst | 7 + docs/source/nsbi.rst | 7 + docs/templates/package.rst_t | 57 +++++ docs/templates/toc.rst_t | 8 + nsbi/__init__.py | 3 + nsbi/_version.py | 1 + pyproject.toml | 54 +++++ setup.cfg | 3 + tests/test_example.py | 2 + 32 files changed, 1173 insertions(+) create mode 100644 .github/CODE_OF_CONDUCT.md create mode 100644 .github/CONTRIBUTING.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/build.yaml create mode 100644 .github/workflows/code_style.yaml create mode 100644 .github/workflows/pr_checks.yaml create mode 100644 .github/workflows/unittests.yaml create mode 100644 .gitignore create mode 100644 .pre-commit-config.yaml create mode 100644 .readthedocs.yml create mode 100644 LICENSE create mode 100755 bin/bump_version.py create mode 100755 bin/check_version.py create mode 100755 bin/dependencies.py create mode 100644 bin/gen_PKGBUILD.py create mode 100755 bin/run_tests create mode 100644 bin/utils.py create mode 100644 docs/Makefile create mode 100644 docs/make.bat create mode 100644 docs/source/conf.py create mode 100644 docs/source/index.rst create mode 100644 docs/source/modules.rst create mode 100644 docs/source/nsbi.rst create mode 100644 docs/templates/package.rst_t create mode 100644 docs/templates/toc.rst_t create mode 100644 nsbi/__init__.py create mode 100644 nsbi/_version.py create mode 100644 pyproject.toml create mode 100644 setup.cfg create mode 100644 tests/test_example.py diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..67e4a5e --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behaviour that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behaviour by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behaviour and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behaviour. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviours that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behaviour may be +reported by contacting the project team at williamjameshandley@gmail.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..8d31776 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,24 @@ +Thank you for considering contributing to nsbi. + +If you have a new feature/bug report, make sure you create an [issue](https://github.com/handley-lab/nsbi/issues), and consult existing ones first, in case your suggestion is already being addressed. + +If you want to go ahead and create the feature yourself, you should fork the repository to you own github account and create a new branch with an appropriate name. Commit any code modifications to that branch, push to GitHub, and then create a pull request via your forked repository. More detail can be found [here](https://gist.github.com/Chaser324/ce0505fbed06b947d962). + +Note that a [code of conduct](https://github.com/handley-lab/nsbi/blob/master/CODE_OF_CONDUCT.md) applies to all spaces managed by the nsbi project, including issues and pull requests. + +## Contributing - `pre-commit` + +nsbi uses black and isort to maintain a consistent style. To speed up linting, contributors can optionally use pre-commit to ensure their commits comply. + +First, ensure that pre-commit, black and isort are installed: +``` +pip install pre-commit black isort +``` +Then install the pre-commit to the .git folder: +``` +pre-commit install +``` +This will check changed files, and abort the commit if they do not comply. To uninstall, run: +``` +pre-commit uninstall +``` diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..f77f578 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,21 @@ +--- +name: Bug report +about: Create a report to help us improve +labels: + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behaviour. + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..751cb5d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,18 @@ +--- +name: Feature request +about: Suggest an idea for this project +labels: + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..e4ab8dc --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,15 @@ +# Description + +Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. + +Fixes # (issue) + +# Checklist: + +- [ ] I have performed a self-review of my own code +- [ ] My code is black compliant (`black . --check`) +- [ ] My code is isort compliant (`isort . --profile black --filter-files`) +- [ ] My code contains compliant docstrings (`pydocstyle --convention=numpy nsbi`) +- [ ] New and existing unit tests pass locally with my changes (`python -m pytest`) +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] I have appropriately incremented the [semantic version number](https://semver.org/) in both README.rst and nsbi/_version.py diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..1e20293 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,169 @@ +name: Build +on: + push: + branches: + - master +jobs: + get-version: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.step1.outputs.v }} + steps: + - uses: actions/checkout@v4 + - name: Get version number + id: step1 + run: echo "v=$(grep ':Version:' README.rst | awk '{print $2}')" >> $GITHUB_OUTPUT + + git-tag-and-release: + runs-on: ubuntu-latest + needs: get-version + steps: + - uses: actions/checkout@v4 + - name: set version number + run: echo "DIST_VERSION=v${{ needs.get-version.outputs.version }}" >> $GITHUB_ENV + - name: Create Tag + uses: actions/github-script@v6 + with: + script: | + const {DIST_VERSION} = process.env + github.rest.git.createRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `refs/tags/${DIST_VERSION}`, + sha: context.sha + }) + - name: Create GitHub Release + uses: ncipollo/release-action@v1 + with: + name: "${{ env.DIST_VERSION }}" + tag: "${{ env.DIST_VERSION }}" + generateReleaseNotes: true + token: ${{ secrets.GITHUB_TOKEN }} + + pypi-release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python 3.9 + uses: actions/setup-python@v4 + with: + python-version: '3.9' + - name: Install pypa/build + run: python -m pip install build --user + - name: Build a binary wheel and a source tarball + run: python -m build --sdist --wheel --outdir dist/ + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_API_TOKEN }} + + pypi-public: + needs: + - get-version + - pypi-release + runs-on: ubuntu-latest + steps: + - name: Wait for PyPi + uses: nev7n/wait_for_response@v1 + with: + url: "https://files.pythonhosted.org/packages/source/n/nsbi/nsbi-${{ needs.get-version.outputs.version }}.tar.gz" + responseCode: 200 + timeout: 600000 + interval: 10000 + + aur-release: + needs: pypi-public + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python 3.9 + uses: actions/setup-python@v4 + with: + python-version: '3.9' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install tomli + - name: Generate PKGBUILD + run: python ./bin/gen_PKGBUILD.py > ./PKGBUILD + - name: Publish AUR package + uses: KSXGitHub/github-actions-deploy-aur@v2.7.0 + with: + pkgname: python-nsbi + pkgbuild: ./PKGBUILD + updpkgsums: True + commit_username: ${{ secrets.AUR_USERNAME }} + commit_email: ${{ secrets.AUR_EMAIL }} + ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} + + conda-release: + needs: pypi-public + name: Build and deploy to conda + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Conda environment creation and activation + uses: conda-incubator/setup-miniconda@v2 + with: + python-version: '3.9' + auto-update-conda: false + auto-activate-base: false + show-channel-urls: true + channels: conda-forge,handley-lab + - name: install dependencies + shell: bash -el {0} + run: conda install grayskull conda-build anaconda-client + - name: Generate meta.yaml from grayskull + shell: bash -el {0} + run: grayskull pypi --strict-conda-forge nsbi + - name: Build and upload the conda packages + uses: uibcdf/action-build-and-upload-conda-packages@v1.2.0 + with: + meta_yaml_dir: nsbi + python-version: '3.9' + user: handley-lab + label: 'main' + token: ${{ secrets.ANACONDA_TOKEN }} # Replace with the right name of your secret + + conda-build: + needs: + - conda-release + - get-version + name: test installation from conda + runs-on: ubuntu-latest + steps: + - name: Conda environment creation and activation + uses: conda-incubator/setup-miniconda@v2 + with: + python-version: '3.9' + auto-update-conda: false + auto-activate-base: false + show-channel-urls: true + - name: install from conda + shell: bash -el {0} + run: conda install -c handley-lab nsbi + - name: get install version + shell: bash -el {0} + run: echo "install_version=$(python -c 'import nsbi; print(nsbi.__version__)')" >> $GITHUB_ENV + - name: fail if versions not matching + if: ${{ env.install_version != needs.get-version.outputs.version }} + run: exit 1 + + pypi-build: + needs: + - pypi-public + - get-version + name: test installation from pypi + runs-on: ubuntu-latest + steps: + - name: Set up Python 3.9 + uses: actions/setup-python@v4 + with: + python-version: '3.9' + - name: Install from pypi + run: pip install nsbi + - name: get install version + run: echo "install_version=$(python -c 'import nsbi; print(nsbi.__version__)')" >> $GITHUB_ENV + - name: fail if versions not matching + if: ${{ env.install_version != needs.get-version.outputs.version }} + run: exit 1 diff --git a/.github/workflows/code_style.yaml b/.github/workflows/code_style.yaml new file mode 100644 index 0000000..fb140b1 --- /dev/null +++ b/.github/workflows/code_style.yaml @@ -0,0 +1,49 @@ +name: Code style + +on: + push: + branches: [master] + pull_request: + branches: [master] + schedule: + - cron: "0 0 * * *" + +jobs: + black: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v4 + with: + python-version: '3.9' + cache: 'pip' + - name: Install black + run: pip install black + - name: Run black + run: black . --check + + isort: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v4 + with: + python-version: '3.9' + cache: 'pip' + - name: Install isort + run: pip install isort + - name: Run isort + run: isort . --check-only + + pydocstyle: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v4 + with: + python-version: '3.9' + cache: 'pip' + - name: Install pydocstyle + run: pip install pydocstyle + - name: run pydocstyle + run: python -m pydocstyle --convention=numpy anesthetic diff --git a/.github/workflows/pr_checks.yaml b/.github/workflows/pr_checks.yaml new file mode 100644 index 0000000..0fb6a57 --- /dev/null +++ b/.github/workflows/pr_checks.yaml @@ -0,0 +1,58 @@ +name: PR checks + +on: + pull_request: + branches: [master] + +jobs: + version-is-unit-incremented: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v4 + with: + python-version: '3.9' + cache: 'pip' + - name: Upgrade pip and install linters + run: | + python -m pip install --upgrade pip + python -m pip install packaging + - name: Check version number + run: python ./bin/check_version.py + + sphinx: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v4 + with: + python-version: '3.9' + cache: 'pip' + - name: Upgrade pip and install doc requirements + run: pip install -e ".[all,docs]" + - name: build documentation + run: | + cd docs + make clean + make html SPHINXOPTS="-W --keep-going -n" + + test-pypi-release: + name: Build and deploy to test PyPI + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v4 + with: + python-version: '3.9' + cache: 'pip' + - name: Install pypa/build + run: pip install build --user + - name: Build a binary wheel and a source tarball + run: python -m build --sdist --wheel --outdir dist/ + - name: Publish to Test PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + continue-on-error: true + with: + password: ${{ secrets.TEST_PYPI_API_TOKEN }} + repository-url: https://test.pypi.org/legacy/ + skip-existing: true diff --git a/.github/workflows/unittests.yaml b/.github/workflows/unittests.yaml new file mode 100644 index 0000000..3ddea9f --- /dev/null +++ b/.github/workflows/unittests.yaml @@ -0,0 +1,29 @@ +name: Unit tests + +on: + push: + branches: [master] + pull_request: + branches: [master] + schedule: + - cron: "0 0 * * *" + +jobs: + pip: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v4 + with: + python-version: '3.9' + cache: 'pip' + - name: Install dependencies + run: pip install -e ".[test]" + + - name: Test with pytest + run: pytest --cov=nsbi tests + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..45bee25 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +__pycache__ +*.pyc +.eggs +.ipynb_checkpoints +dist +*.egg-info/ +build +*~ +.pytest_cache/* +.coverage +venv diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..05e7122 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,23 @@ +repos: + - repo: local + hooks: + - id: pydocstyle + name: pydocstyle + entry: python -m pydocstyle --convention=numpy + language: system + types: [python] + files: "nsbi/" + - id: black + name: black + args: [ "--check" ] + entry: black + language: system + types: [python] + files: "." + - id: isort + name: isort + entry: isort + args: [ "--profile", "black", "--filter-files", "--check-only" ] + language: system + types: [python] + files: "." diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 0000000..594c91f --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,25 @@ +# .readthedocs.yml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the version of Python and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.8" + +# Build documentation in the docs/ directory with Sphinx +sphinx: + builder: html + configuration: docs/source/conf.py + fail_on_warning: true + +python: + install: + - method: pip + path: . + extra_requirements: + - docs,all diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6a9f9dd --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Will Handley + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/bin/bump_version.py b/bin/bump_version.py new file mode 100755 index 0000000..1e49457 --- /dev/null +++ b/bin/bump_version.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +import sys + +from packaging import version +from utils import run + +vfile = "nsbi/_version.py" +README = "README.rst" + +current_version = run("cat", vfile) +current_version = current_version.split("=")[-1].strip().strip('"') +current_version = version.parse(current_version) + +if len(sys.argv) > 1: + update_type = sys.argv[1] +else: + update_type = "micro" + +major = current_version.major +minor = current_version.minor +micro = current_version.micro + +if update_type == "micro": + micro += 1 +elif update_type == "minor": + minor += 1 + micro = 0 +elif update_type == "major": + major += 1 + minor = 0 + micro = 0 + +new_version = version.parse(f"{major}.{minor}.{micro}") + +sed_string = f"s/{current_version}/{new_version}/g".replace(".", "\.") + +for f in [vfile, README]: + run("sed", "-i", sed_string, f) + +run("git", "add", vfile, README) +run("git", "commit", "-m", f"bump version to {new_version}") diff --git a/bin/check_version.py b/bin/check_version.py new file mode 100755 index 0000000..c23bfc8 --- /dev/null +++ b/bin/check_version.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python +import sys + +from packaging import version +from utils import run, unit_incremented + +vfile = "nsbi/_version.py" +README = "README.rst" +current_version = run("cat", vfile) +current_version = current_version.split("=")[-1].strip().strip('"') + +run("git", "fetch", "origin", "master") +previous_version = run("git", "show", "remotes/origin/master:" + vfile) +previous_version = previous_version.split("=")[-1].strip().strip('"') +readme_version = run("grep", ":Version:", README) +readme_version = readme_version.split(":")[-1].strip() +current_version + +if version.parse(current_version) != version.parse(readme_version): + sys.stderr.write("Version mismatch: {} != {}".format(vfile, README)) + sys.exit(1) + +elif previous_version == "": + sys.exit(0) + +elif not unit_incremented(current_version, previous_version): + sys.stderr.write( + ( + "Version must be incremented by one:\n" "HEAD: {},\n" "master: {}.\n" + ).format(current_version, previous_version) + ) + sys.exit(1) diff --git a/bin/dependencies.py b/bin/dependencies.py new file mode 100755 index 0000000..3d6aee9 --- /dev/null +++ b/bin/dependencies.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python +import tomli + +with open("pyproject.toml", "rb") as f: + pyproject = tomli.load(f) + +print(" ".join(pyproject["project"]["dependencies"])) diff --git a/bin/gen_PKGBUILD.py b/bin/gen_PKGBUILD.py new file mode 100644 index 0000000..73bc509 --- /dev/null +++ b/bin/gen_PKGBUILD.py @@ -0,0 +1,49 @@ +import tomli + +with open("pyproject.toml", "rb") as f: + pyproject = tomli.load(f) + +description = pyproject["project"]["description"] +version = open("nsbi/_version.py", "r").readlines()[0].split("=")[1].strip().strip('"') +url = pyproject["project"]["urls"]["Homepage"] +pyproject["project"]["dependencies"] +rel = 1 + + +PKGBUILD = """# Maintainer: Will Handley (aur.archlinux.org/account/wjhandley) +pkgname=python-nsbi +_name=${pkgname#python-} +pkgver=%s +pkgrel=%s +pkgdesc="%s" +arch=(any) +url="%s" +license=(MIT) +groups=() +depends=(python-numpy python-matplotlib python-scipy python-pandas) +makedepends=(python-build python-installer) +provides=(nsbi) +conflicts=() +replaces=() +backup=() +options=(!emptydirs) +install= +source=("https://files.pythonhosted.org/packages/source/${_name::1}/$_name/$_name-$pkgver.tar.gz") +sha256sums=(XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX) + +build() { + cd "$srcdir/$_name-$pkgver" + python -m build --wheel --no-isolation +} + +package() { + cd "$srcdir/$_name-$pkgver" + python -m installer --destdir="$pkgdir" dist/*.whl +} +""" % ( + version, + rel, + description, + url, +) +print(PKGBUILD) diff --git a/bin/run_tests b/bin/run_tests new file mode 100755 index 0000000..6d4669f --- /dev/null +++ b/bin/run_tests @@ -0,0 +1,13 @@ +#!/bin/bash + +echo "Running black check" +black --check . + +echo "Running isort check" +isort --check-only --profile black . + +echo "Running docstring checks" +pydocstyle --convention=numpy nsbi + +echo "Running code tests" +python -m pytest diff --git a/bin/utils.py b/bin/utils.py new file mode 100644 index 0000000..1a08b36 --- /dev/null +++ b/bin/utils.py @@ -0,0 +1,40 @@ +import subprocess + +from packaging import version + + +def unit_incremented(a, b): + """Check if a is one version larger than b.""" + a = version.parse(a) + b = version.parse(b) + if a.pre is not None and b.pre is not None: + if a.pre[0] == b.pre[0]: + return a.pre[1] == b.pre[1] + 1 and a.base_version == b.base_version + else: + return ( + a.pre[1] == 0 + and a.pre[0] > b.pre[0] + and a.base_version == b.base_version + ) + elif a.pre is not None: + return a.base_version > b.base_version and a.pre[1] == 0 + elif b.pre is not None: + return a.base_version == b.base_version + else: + return ( + a.micro == b.micro + 1 + and a.minor == b.minor + and a.major == b.major + or a.micro == 0 + and a.minor == b.minor + 1 + and a.major == b.major + or a.micro == 0 + and a.minor == 0 + and a.major == b.major + 1 + ) + + +def run(*args): + return subprocess.run( + args, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ).stdout diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..69fe55e --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,19 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.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). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..543c6b1 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..e9bee92 --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,246 @@ +# -*- coding: utf-8 -*- +# +# Configuration file for the Sphinx documentation builder. +# +# This file does only contain a selection of the most common options. For a +# full list see the documentation: +# http://www.sphinx-doc.org/en/master/config + +# -- Path setup -------------------------------------------------------------- + +import os +import sys + +sys.path.append(os.path.abspath("../../")) + + +def get_version(short=False): + with open("../../README.rst") as f: + for line in f: + if ":Version:" in line: + ver = line.split(":")[2].strip() + if short: + subver = ver.split(".") + return "%s.%s" % tuple(subver[:2]) + else: + return ver + + +# -- Project information ----------------------------------------------------- + +project = "nsbi" +copyright = "2024, Will Handley" +author = "Will Handley" + +# The short X.Y version +version = get_version(True) +# The full version, including alpha/beta/rc tags +release = get_version() + + +# -- General configuration --------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.autosectionlabel", + "sphinx.ext.doctest", + "sphinx.ext.intersphinx", + "sphinx.ext.todo", + "sphinx.ext.coverage", + "sphinx.ext.mathjax", + "sphinx.ext.ifconfig", + "sphinx.ext.viewcode", + "sphinx.ext.githubpages", + "sphinx.ext.imgconverter", + "matplotlib.sphinxext.plot_directive", + "numpydoc", +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = ".rst" + +# The master toctree document. +master_doc = "index" + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = "en" + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = "sphinx" + + +# -- Options for autodoc ------------------------------------------------- +autodoc_default_options = { + "members": True, + "undoc-members": True, +} + +autosummary_generate = True + +nitpick_ignore = [ + ("py:obj", "pandas.core.groupby.SeriesGroupBy.sample") +] # not currently included in pandas 1.5, but will in future + +# -- Options for autosectionlabel------------------------------------------ +autosectionlabel_prefix_document = True + +# -- Options for numpydoc ------------------------------------------------- +numpydoc_show_inherited_class_members = False +numpydoc_show_class_members = False + + +# -- Options for matplotlib extension ---------------------------------------- + +plot_rcparams = {"savefig.bbox": "tight"} +plot_apply_rcparams = True # if context option is used +plot_include_source = True +plot_html_show_source_link = False +plot_html_show_formats = False + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +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 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'] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# The default sidebars (for documents that don't match any pattern) are +# defined by theme itself. Builtin themes are using these templates by +# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', +# 'searchbox.html']``. +# +# html_sidebars = {} + + +# -- Options for HTMLHelp output --------------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = "nsbidoc" + + +# -- Options for LaTeX output ------------------------------------------------ + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, "nsbi.tex", "nsbi Documentation", "Will Handley", "manual"), +] + + +# -- Options for manual page output ------------------------------------------ + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [(master_doc, "nsbi", "nsbi Documentation", [author], 1)] + + +# -- Options for Texinfo output ---------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ( + master_doc, + "nsbi", + "nsbi Documentation", + author, + "nsbi", + "Simulation Based Inference with Nested Sampling", + "Miscellaneous", + ), +] + + +# -- Options for Epub output ------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = project +epub_author = author +epub_publisher = author +epub_copyright = copyright + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +# +# epub_identifier = '' + +# A unique identification for the text. +# +# epub_uid = '' + +# A list of files that should not be packed into the epub file. +epub_exclude_files = ["search.html"] + + +# -- Extension configuration ------------------------------------------------- + +# -- Options for intersphinx extension --------------------------------------- + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = { + "numpy": ("https://numpy.org/doc/stable", None), + "scipy": ("https://docs.scipy.org/doc/scipy", None), + "pandas": ("https://pandas.pydata.org/pandas-docs/stable", None), + "matplotlib": ("https://matplotlib.org/stable", None), + "getdist": ("https://getdist.readthedocs.io/en/latest/", None), +} + +# -- Options for todo extension ---------------------------------------------- + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..4598664 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,10 @@ +.. title:: Introduction + +.. toctree:: + :maxdepth: 1 + :caption: Contents + + nsbi + +.. include:: ../../README.rst + diff --git a/docs/source/modules.rst b/docs/source/modules.rst new file mode 100644 index 0000000..7d366b7 --- /dev/null +++ b/docs/source/modules.rst @@ -0,0 +1,7 @@ +nsbi +==== + +.. toctree:: + :maxdepth: 5 + + nsbi diff --git a/docs/source/nsbi.rst b/docs/source/nsbi.rst new file mode 100644 index 0000000..cc71dd5 --- /dev/null +++ b/docs/source/nsbi.rst @@ -0,0 +1,7 @@ +nsbi package +============ + +.. automodule:: nsbi + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/templates/package.rst_t b/docs/templates/package.rst_t new file mode 100644 index 0000000..979cfc1 --- /dev/null +++ b/docs/templates/package.rst_t @@ -0,0 +1,57 @@ +{%- macro automodule(modname, options) %} +.. automodule:: {{ modname }} +{%- for option in options %} + :{{ option }}: +{%- endfor %} +{%- endmacro %} + +{%- macro toctree(docnames, maxdepth=4) %} +.. toctree:: + :maxdepth: {{ maxdepth }} +{% for docname in docnames %} + {{ docname }} +{%- endfor %} +{% endmacro %} + +{%- if is_namespace %} +{{- [pkgname, "namespace"] | join(" ") | e | heading(1) }} +{%- else %} +{{- [pkgname, "package"] | join(" ") | e | heading(1) }} +{%- endif %} + +{%- if is_namespace %} +.. py:module:: {{ pkgname }} +{% endif %} + +{%- if modulefirst and not is_namespace %} +{{ automodule(pkgname, automodule_options) }} +{% endif %} + +{%- if subpackages %} +{{ [pkgname, "subpackages"] | join(" ") | e | heading(2) }} +{{ toctree(subpackages, 1) }} +{% if submodules %} +{{ [pkgname, "modules"] | join(" ") | e | heading(2) }} +{%- endif %} +{%- endif %} + +{%- if submodules %} +{%- if separatemodules %} +{{ toctree(submodules) }} +{%- else %} +{%- for submodule in submodules %} +{% if show_headings %} +{%- if subpackages %} +{{ [submodule, "module"] | join(" ") | e | heading(3) }} +{%- else %} +{{ [submodule, "module"] | join(" ") | e | heading(2) }} +{%- endif %} +{%- endif %} +{{ automodule(submodule, automodule_options) }} +{% endfor %} +{% endif %} +{% endif %} + +{%- if not modulefirst and not is_namespace %} +{{ automodule(pkgname, automodule_options) }} +{% endif %} diff --git a/docs/templates/toc.rst_t b/docs/templates/toc.rst_t new file mode 100644 index 0000000..f1190c2 --- /dev/null +++ b/docs/templates/toc.rst_t @@ -0,0 +1,8 @@ +{{ header | heading }} + +.. toctree:: + :maxdepth: 5 +{% for docname in docnames %} + {{ docname }} +{%- endfor %} + diff --git a/nsbi/__init__.py b/nsbi/__init__.py new file mode 100644 index 0000000..8529ca1 --- /dev/null +++ b/nsbi/__init__.py @@ -0,0 +1,3 @@ +"""nsbi: Simulation Based Inference with Nested Sampling""" + +from nsbi._version import __version__ # noqa: F401 diff --git a/nsbi/_version.py b/nsbi/_version.py new file mode 100644 index 0000000..6c8e6b9 --- /dev/null +++ b/nsbi/_version.py @@ -0,0 +1 @@ +__version__ = "0.0.0" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..bd41e7b --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,54 @@ +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[project] +name = "nsbi" +dynamic = ["version"] +authors = [ + { name="Will Handley", email="williamjameshandley@gmail.com" }, +] +description = "Simulation Based Inference with Nested Sampling" +readme = "README.rst" +license = {file = "LICENSE"} +requires-python = ">=3.8" +dependencies = [ + 'numpy', + 'scipy', + 'matplotlib', +] +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Development Status :: 1 - Planning", + "Intended Audience :: Developers", + "Intended Audience :: Science/Research", + "Natural Language :: English", + "Topic :: Scientific/Engineering", + "Topic :: Scientific/Engineering :: Astronomy", + "Topic :: Scientific/Engineering :: Physics", + "Topic :: Scientific/Engineering :: Visualization", + "Topic :: Scientific/Engineering :: Information Analysis", + "Topic :: Scientific/Engineering :: Mathematics", +] + +[project.urls] +"Homepage" = "https://github.com/handley-lab/nsbi" +"Bug Tracker" = "https://github.com/handley-lab/nsbi/issues" +"Documentation" = "https://nsbi.readthedocs.io/en/latest/" + +[project.optional-dependencies] +docs = ["sphinx", "sphinx_rtd_theme", "numpydoc"] +test = ["pytest", "pytest-cov", "flake8", "pydocstyle", "packaging", "pre-commit"] + +[tool.setuptools.dynamic] +version = {attr = "nsbi._version.__version__"} + +[tool.flake8] +max-line-length = 88 +extend-ignore = ['E203', 'W503'] + +[tool.isort] +profile = 'black' +skip_gitignore = true diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..b8e6ce8 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,3 @@ +[options] +packages = find: +include_package_data = True diff --git a/tests/test_example.py b/tests/test_example.py new file mode 100644 index 0000000..813df60 --- /dev/null +++ b/tests/test_example.py @@ -0,0 +1,2 @@ +def test_example(): + assert True