From 236bc25ea036102ce3808e5b6cb648919150fb37 Mon Sep 17 00:00:00 2001 From: Jeff Jennings Date: Mon, 12 Aug 2024 11:39:37 -0400 Subject: [PATCH] Initial commit --- .github/ISSUE_TEMPLATE/bug_report.md | 24 +++ .github/ISSUE_TEMPLATE/feature_request.md | 16 ++ .github/workflows/docs.yml | 108 +++++++++++++ .github/workflows/format_lint.yml | 30 ++++ .github/workflows/package.yml | 42 +++++ .github/workflows/tests.yml | 36 +++++ .github/workflows/type_check.yml | 32 ++++ .gitignore | 164 ++++++++++++++++++++ HISTORY.rst | 11 ++ LICENSE.rst | 36 +++++ MANIFEST.in | 7 + README.md | 10 ++ docs/Makefile | 20 +++ docs/_static/custom.css | 11 ++ docs/_templates/layout.html | 5 + docs/conf.py | 84 ++++++++++ docs/getting_started.rst | 54 +++++++ docs/index.rst | 22 +++ docs/make.bat | 35 +++++ docs/py_API.rst | 8 + example_setup_files/ minimal_pyproject.toml | 15 ++ example_setup_files/README.md | 1 + example_setup_files/minimal_setup.py | 18 +++ py_template/__init__.py | 1 + py_template/example_module.py | 50 ++++++ pyproject.toml | 64 ++++++++ test/__init__.py | 1 + test/test_example_module.py | 19 +++ 28 files changed, 924 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/workflows/docs.yml create mode 100644 .github/workflows/format_lint.yml create mode 100644 .github/workflows/package.yml create mode 100644 .github/workflows/tests.yml create mode 100644 .github/workflows/type_check.yml create mode 100644 .gitignore create mode 100644 HISTORY.rst create mode 100644 LICENSE.rst create mode 100644 MANIFEST.in create mode 100644 README.md create mode 100644 docs/Makefile create mode 100644 docs/_static/custom.css create mode 100644 docs/_templates/layout.html create mode 100644 docs/conf.py create mode 100644 docs/getting_started.rst create mode 100644 docs/index.rst create mode 100644 docs/make.bat create mode 100644 docs/py_API.rst create mode 100644 example_setup_files/ minimal_pyproject.toml create mode 100644 example_setup_files/README.md create mode 100644 example_setup_files/minimal_setup.py create mode 100644 py_template/__init__.py create mode 100644 py_template/example_module.py create mode 100644 pyproject.toml create mode 100644 test/__init__.py create mode 100644 test/test_example_module.py diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..3124e92 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,24 @@ +--- +name: Bug report +about: Describe a problem you're having with the code +title: '' +labels: bug +assignees: '' + +--- + +**Describe the bug -** +1. Include the full Traceback of the error. + +**To reproduce -** +1. Include a simple, standalone code snippet that demonstrates the issue. + +**Expected behavior -** + +**Your setup -** + - code version: + - Python version: + - Operating system: + +**Additional context -** +(E.g., any peculiarity of the data or of the problem you're trying to solve?) \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..146eef6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,16 @@ +--- +name: Feature request +about: Suggest an idea for improving/expanding the code +title: '' +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe -** + +**Describe the solution you'd like -** + +**Describe alternatives you've considered -** + +**Additional context -** \ No newline at end of file diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..a8f5e6e --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,108 @@ +name: Docs + +on: + push: + branches: + - main +# when a review is requested on a PR that targets `main`, or the PR is closed: + # pull_request: + # types: [review_requested, closed] + +# Prevent multiple PRs from building/deploying the docs at the same time +concurrency: + group: ${{ github.workflow }} + +jobs: + docs-build: + name: Build docs + runs-on: ubuntu-latest + + steps: + - name: Check out repo + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.11 + + - name: Display Python version + run: python -c "import sys; print(sys.version)" + + - name: Install docs dependencies + run: | + sudo apt-get install pandoc + pip install setuptools --upgrade + pip install .[test,docs] + + - name: Make docs + run: | + pip install . + cd docs + make html + + # check code coverage, store results in the built docs. + # coverage.py creates a .gitignore with '*' where it's run; remove it + # to keep the coverage report and badge on gh-pages + - name: Coverage + # only continue if the PR is not just closed, but also merged: + # if: github.event.pull_request.merged == true + run: | + coverage run --source=py_template -m pytest test/*.py + coverage report + mkdir -p docs/_build/html/coverage + coverage html -d docs/_build/html/coverage + rm docs/_build/html/coverage/.gitignore + coverage-badge -f -o docs/_build/html/coverage/badge.svg + + # upload the built docs as an artifact so the files can be accessed + # by a subsequent job in the workflow. + # only store the artifact for 'retention-days' + - name: Upload docs artifact + # if: github.event.pull_request.merged == true + uses: actions/upload-artifact@v4 + with: + name: built_docs + path: docs/_build/html + retention-days: 1 + + docs-deploy: + name: Deploy docs + needs: docs-build + # if: github.event.pull_request.merged == true + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + # download the previously uploaded 'built_docs' artifact + - name: Download docs artifact + uses: actions/download-artifact@v4 + id: download + with: + name: built_docs + path: docs/_build/html + + - name: Echo download path + run: echo ${{steps.download.outputs.download-path}} + + - name: Disable jekyll builds + run: touch docs/_build/html/.nojekyll + + - name: Display docs file structure + run: ls -aR + working-directory: docs/_build/html + + - name: Install and configure dependencies + run: | + npm install -g --silent gh-pages@2.0.1 + + - name: Deploy docs to gh-pages branch + run: | + git remote set-url origin https://git:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git + npx gh-pages --dotfiles --dist docs/_build/html --user "github-actions-bot " --message "Update docs [skip ci]" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/format_lint.yml b/.github/workflows/format_lint.yml new file mode 100644 index 0000000..90ef136 --- /dev/null +++ b/.github/workflows/format_lint.yml @@ -0,0 +1,30 @@ +name: Coding standards +on: push + +jobs: + check_code_standards: + name: Format, lint code + runs-on: ubuntu-latest + + steps: + - name: Check out repo + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.11 + + - name: Display Python version + run: python -c "import sys; print(sys.version)" + + - name: Install dependencies + run: | + pip install setuptools --upgrade + pip install .[coding_standards] + + - name: Install package + run: pip install . + + - name: Run `ruff` linter / formatter + run: ruff check . \ No newline at end of file diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml new file mode 100644 index 0000000..098b052 --- /dev/null +++ b/.github/workflows/package.yml @@ -0,0 +1,42 @@ +name: build and release to PyPI + +on: + release: + types: + - released + +jobs: + deploy: + runs-on: ubuntu-20.04 + + steps: + - name: Check out repo + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.11 + + - name: Display Python version + run: python -c "import sys; print(sys.version)" + + - name: Install dependencies + run: | + pip install --upgrade pip + pip install setuptools wheel twine + pip install pep517 --user + + - name: Install package + run: | + pip install . + + - name: Build binary wheel and source tarball + run: | + python -m pep517.build --source --binary --out-dir dist/ . + + - name: Publish distribution to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.pypi_password }} \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..150d4cb --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,36 @@ +name: Tests +on: push + +jobs: + test: + name: Run tests + runs-on: ubuntu-latest + + strategy: + matrix: + python-version: ['3.10', '3.11', '3.12'] + + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Display Python version + run: python -c "import sys; print(sys.version)" + + - name: Install test dependencies + run: | + pip install setuptools --upgrade + pip install .[test] + + - name: Install package + run: pip install . + + - name: Run unit tests + run: | + mkdir -p test-reports + py.test -v --junitxml=test-reports/junit.xml test/*.py diff --git a/.github/workflows/type_check.yml b/.github/workflows/type_check.yml new file mode 100644 index 0000000..733a878 --- /dev/null +++ b/.github/workflows/type_check.yml @@ -0,0 +1,32 @@ +name: Type checking +on: push + +jobs: + type_check: + name: Type check code with mypy + runs-on: ubuntu-latest + + steps: + - name: Check out repo + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.11 + + - name: Display Python version + run: python -c "import sys; print(sys.version)" + + - name: Install dependencies + run: | + pip install setuptools --upgrade + pip install .[type_checking] + + - name: Install package + run: pip install . + + - name: Run `mypy` type checker + # proceed even if mypy checking fails: + continue-on-error: true + run: mypy --strict . \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a19dd50 --- /dev/null +++ b/.gitignore @@ -0,0 +1,164 @@ +**/.DS_Store +*.egg +*.egg-info/ + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST +py_template/version.py +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/HISTORY.rst b/HISTORY.rst new file mode 100644 index 0000000..132bbfb --- /dev/null +++ b/HISTORY.rst @@ -0,0 +1,11 @@ +.. :history: + +Changelog ++++++++++ + +v.0.0.1 ++++++++ +* One-line summary of major changes* + +- Detailed change 1 +- Detailed change 2 diff --git a/LICENSE.rst b/LICENSE.rst new file mode 100644 index 0000000..ffc31bb --- /dev/null +++ b/LICENSE.rst @@ -0,0 +1,36 @@ +`py_template` author: Jeff Jennings (jjennings@flatironinstitute.org) + +This package is based upon +the `OpenAstronomy packaging guide `_ +which is licensed under the BSD 3-clause license. + +This project is based upon the `Astropy package template `_ which is licensed under the terms +of the following license. + +--- + +Copyright (c) 2018, Astropy Developers +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. +* Neither the name of the Astropy Team nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..851940c --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,7 @@ +# Exclude specific files +# All files which are tracked by git and not explicitly excluded here are included by setuptools_scm +# Prune folders +prune build +prune docs/_build +prune docs/api +global-exclude *.pyc *.o diff --git a/README.md b/README.md new file mode 100644 index 0000000..1dd8375 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +Template Python repository +-------------------------- + +[![Tests](https://github.com/CCA-Software-Group/py_template/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/CCA-Software-Group/py_template/actions/workflows/tests.yml) +[![Coverage](https://cca-software-group.github.io/py_template/coverage/badge.svg)](https://cca-software-group.github.io/py_template/coverage/index.html) +[![Docs](https://github.com/CCA-Software-Group/py_template/actions/workflows/docs.yml/badge.svg)](https://cca-software-group.github.io/py_template/) + +This is a basic template for a Python repository, with the components needed to develop and release an open source software package. These include unit tests; type annotations; a documentation site with an API; and continuous integration for tests, code coverage, linting/formatting, type checking, publishing the docs, and releasing the software as a `pip`-installable package. + +See the [docs](https://cca-software-group.github.io/py_template/) for a 'Getting Started' guide with a description of the repo's contents, steps on how to make your own Python repo using this template, and what to do next to develop your package. diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d4bb2cb --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +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) diff --git a/docs/_static/custom.css b/docs/_static/custom.css new file mode 100644 index 0000000..237effd --- /dev/null +++ b/docs/_static/custom.css @@ -0,0 +1,11 @@ +.wy-side-nav-search, .wy-nav-top { + background: #0CCF88; + } + +.wy-menu > .caption > span.caption-text { +color: #0CCF88; +} + +.nbinput .prompt, .nboutput .prompt { + display: none; +} \ No newline at end of file diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html new file mode 100644 index 0000000..e08d6a5 --- /dev/null +++ b/docs/_templates/layout.html @@ -0,0 +1,5 @@ +{# layout.html #} +{# Import the layout of the theme. #} +{% extends "!layout.html" %} + +{% set css_files = css_files + ['_static/custom.css'] %} \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..6978a79 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,84 @@ +# 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 + +import datetime + +# -- Project information ----------------------------------------------------- + +# The full version, including alpha/beta/rc tags +from py_template import __version__ + +release = __version__ + +project = "py_template" +author = "Jeff Jennings" +copyright = f"{datetime.datetime.now().year}, {author}" # noqa: A001 + +# -- General configuration --------------------------------------------------- + +# 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.intersphinx", + "sphinx.ext.todo", + "sphinx.ext.coverage", + "sphinx.ext.inheritance_diagram", + "sphinx.ext.viewcode", + "sphinx.ext.napoleon", + "sphinx.ext.doctest", + "sphinx.ext.mathjax", + "sphinx_automodapi.automodapi", + "sphinx_automodapi.smart_resolver", + "sphinx_rtd_theme", +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# 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 = ["_build", "Thumbs.db", ".DS_Store"] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +source_suffix = ".rst" + +# The master toctree document. +master_doc = "index" + +# Treat everything in single ` as a Python reference. +default_role = "py:obj" + +# -- Options for intersphinx extension --------------------------------------- + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {"python": ("https://docs.python.org/", None)} + +# -- 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" + +# 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"] +html_static_path = ["_static"] + +# By default, when rendering docstrings for classes, sphinx.ext.autodoc will +# make docs with the class-level docstring and the class-method docstrings, +# but not the __init__ docstring, which often contains the parameters to +# class constructors across the scientific Python ecosystem. The option below +# will append the __init__ docstring to the class-level docstring when rendering +# the docs. For more options, see: +# https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#confval-autoclass_content +autoclass_content = "both" + +# -- Other options ---------------------------------------------------------- diff --git a/docs/getting_started.rst b/docs/getting_started.rst new file mode 100644 index 0000000..98d26c6 --- /dev/null +++ b/docs/getting_started.rst @@ -0,0 +1,54 @@ +Getting started +=============== + +Repo file structure +------------------- +| The `py_template` repo has a basic file structure: +| 1) `.github` has two subfolders: +| * `ISSUE_TEMPLATE` has files that are templates users can select from when opening an issue in the repo on GitHub +| * `workflows` implements continuous integration (CI) through GitHub 'actions' that are automatically run according to a chosen trigger. These are currently: +| - `docs.yml` builds and deploys this docs site when a push is made to `main`. +| - `format_lint.yml` lints and formats the code on each push using *ruff*. +| - `package.yml` releases the package to PyPI on each *release* (create a release from the repo's main GitHub page). This makes the latest release version of the package *pip*-installable. For a guide on how to first reserve a name for your project on PyPI (necessary for this workflow), see the `Python packaging guide `_. +| - `tests.yml` runs tests with *pytest* on each push. +| - `type_check.yml` runs type checking with *mypy* on each push. The CI continues even if the type checker finds errors. +| 2) `docs` has the files used to build the docs with *Sphinx*, with the site content in `index.rst` and `py_API.rst`. +| 3) `py_template` is the source code folder, with the necessary `__init__.py` and example code file, `example_module.py`. +| 4) `test` has the unit tests, with example tests for the source code file in `test_example_module.py`. When you add new test files, they should start with `test_` so *pytest* recognizes them. +| 5) `.gitignore` is a list of file types that are ignored when you push to the remote repo. +| 6) `HISTORY.rst` is the change log that you should update as you implement the packaged version. +| 7) `LICENSE.rst` has the package's license. +| 8) `MANIFEST.in` has instructions for how to pre-process the package (which files to exclude) when preparing to release it to PyPI (the Python Package Index). Packages uploaded to PyPI can be installed by users with *pip*. +| 9) `README.md` is the file you're reading! It has badges that use the CI to display if the unit tests are passing, what percentage of the code is covered by the tests, and if the docs build and deploy is passing. +| 10) `pyproject.toml` is the configuration file for packaging the software. See the `Python docs `_ for a description of its contents. See also the `example_setup_files` directory for an example minimal `pyproject.toml` and the analogous `setup.py`. + +Making a new Python repo using `py_template` +-------------------------------------------- +| 1) Click the 'Use this template' button at the top-right of the GitHub page. +| 2) In your new repo, rename (search and replace) all instances of "py_template" with the name for your package. +| 3a) If you create a public repo, to set up your docs deployment CI: from your repo page, go to 'Settings' then 'Pages', then set 'Source' as 'Deploy from a branch' and set 'Branch' as 'gh-pages'. Your docs will now deploy according to the trigger in `.github/workflows/docs.yml` +| 3b) If you create a private repo, the docs build will fail because private repos can't have a public docs page. You can disable the docs build and deploy workflow in `.github/workflows/docs.yml` +| 4) Update the `authors` field in `pyproject.toml`, `docs/conf.py` and `LICENSE.rst` (the template is partially based on the `OpenAstronomy packaging guide `_, so please retain that aspect of the license). + +Interacting with your new code +------------------------------ +| After cloning your repo to your computer, from the project's root directory, you can: +| 1) Install your package with all optional dependencies: +| `pip install -e ".[dev]"` +| 2) Run your tests: +| `pytest .` +| 3) Run linting and formatting to see their effects: +| `ruff check .` +| 4) Run type checking using mypy: +| `mypy --strict .` +| 5) Build your docs locally: +| `cd docs; make html` +| After building the docs, view them with +| `open docs/_build/html/index.html` + +Developing your package +----------------------- +| 1) Add new unit tests in `test/test_*.py` for new functions and classes. Test not just whether the new code runs, but also if it gives a sensible result. +| 2) Update the docs, including the main page (`docs/index.rst`), adding pages, and updating the API (`docs/py_API.rst`) when you add new functions and classes. +| 3) Optionally change the CI triggers for each of the actions in `.github/workflows`. +| 4) Update the changelog in `HISTORY.rst` when you're ready to release your first version of the code! \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..f92228d --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,22 @@ +Documentation +============= + +Welcome to the `py_template` docs! + +See the 'getting started' guide for steps on how to make your own Python repo using this template and considerations for building your own package. + +The API has example entries for the function and class in `example_module.py`. + +.. toctree:: + :maxdepth: 1 + :caption: Tutorials + + Getting started guide + +.. toctree:: + :maxdepth: 1 + :caption: Reference + + API + Index + GitHub repo \ No newline at end of file diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..2119f51 --- /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=. +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% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/py_API.rst b/docs/py_API.rst new file mode 100644 index 0000000..0957783 --- /dev/null +++ b/docs/py_API.rst @@ -0,0 +1,8 @@ +example_module +============== + +.. currentmodule:: py_template.example_module + +.. autoclass:: SoftwareGroup + +.. autofunction:: fibonacci diff --git a/example_setup_files/ minimal_pyproject.toml b/example_setup_files/ minimal_pyproject.toml new file mode 100644 index 0000000..4b51b1b --- /dev/null +++ b/example_setup_files/ minimal_pyproject.toml @@ -0,0 +1,15 @@ +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[project] +name = "py_template" +description = "Template Python repository" +dynamic = ["version"] +readme = { file = "README.rst", content-type = "text/x-rst" } +license = { file = "LICENSE.rst", content-type = "text/plain" } +authors = [ + { name = "Jeff Jennings", email = "jjennings@flatironinstitute.org" }, +] +requires-python = ">=3.10" +dependencies = ["numpy>=1.24"] \ No newline at end of file diff --git a/example_setup_files/README.md b/example_setup_files/README.md new file mode 100644 index 0000000..1984500 --- /dev/null +++ b/example_setup_files/README.md @@ -0,0 +1 @@ +It's best to use a `pyproject.toml` file for packaging. For guidance on converting from a `setup.py` file to a `pyproject.toml` file, see the [Python docs](https://packaging.python.org/en/latest/guides/modernize-setup-py-project/). \ No newline at end of file diff --git a/example_setup_files/minimal_setup.py b/example_setup_files/minimal_setup.py new file mode 100644 index 0000000..5ba7631 --- /dev/null +++ b/example_setup_files/minimal_setup.py @@ -0,0 +1,18 @@ +from setuptools import find_packages, setup + +with open("README.md", "r") as f: + long_description = f.read() + +setup( + name="py_template", + description="Template Python repository", + version="0.0.1", + long_description=long_description, + long_description_content_type="text/markdown", + license_files=("LICENSE.rst",), + author="Jeff Jennings", + author_email="jjennings@flatironinstitute.org", + packages=find_packages(), + python_requires=">=3.10", + install_requires=["numpy >= 1.24"], +) \ No newline at end of file diff --git a/py_template/__init__.py b/py_template/__init__.py new file mode 100644 index 0000000..f102a9c --- /dev/null +++ b/py_template/__init__.py @@ -0,0 +1 @@ +__version__ = "0.0.1" diff --git a/py_template/example_module.py b/py_template/example_module.py new file mode 100644 index 0000000..697ca22 --- /dev/null +++ b/py_template/example_module.py @@ -0,0 +1,50 @@ +def fibonacci(n: int) -> list[int]: + """ + Example function. + + Parameters + ---------- + n : int + Integer term up to which the sequence is calculated + + Returns + ------- + fib_seq : list[int] + The Fibonacci sequence up to term `n` + """ + fib_seq = [] + + a, b = 0, 1 + + for i in range(n): + a, b = b, a + b + fib_seq.append(a) + + return fib_seq + + +class SoftwareGroup: + """ + Example class. + + Parameters + ---------- + people : str + Group members + purpose : str + Group objectives + """ + + def __init__(self, people: str, purpose: str) -> None: + self.people = people + self.purpose = purpose + self.meeting_format = "hack session + office hour + periodic tutorial" + + def long_term_goals(self) -> str: + research_goal = "develop open-source research software for astronomy" + community_goal = "improve diversity in astro software positions" + education_goal = "organize and run astro software workshops" + cca_goal = "improve software knowledge and practices at CCA" + return ", ".join( + [self.purpose, research_goal, community_goal, education_goal, cca_goal] + ) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..12eee15 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,64 @@ +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[project] +name = "py_template" +description = "Template Python repository" +dynamic = ["version"] +readme = { file = "README.rst", content-type = "text/x-rst" } +license = { file = "LICENSE.rst", content-type = "text/plain" } +authors = [ + { name = "Jeff Jennings", email = "jjennings@flatironinstitute.org" }, +] +requires-python = ">=3.10" +dependencies = ["numpy>=1.24"] + +[project.optional-dependencies] +test = [ + "pytest", + "coverage", + "coverage-badge", +] +docs = [ + "sphinx", + "sphinx-automodapi", + "sphinx_rtd_theme", + "sphinxcontrib-fulltoc", + "nbsphinx", + "jupyter_client", + "ipykernel", +] +coding_standards = [ + "ruff", +] +type_checking = [ + "mypy", +] +dev = ["py_template[test, docs, coding_standards]"] + +[tool.setuptools] +zip-safe = false +include-package-data = true + +[tool.setuptools.packages.find] + +[tool.pytest.ini_options] +testpaths = [ + "test", + "docs", +] + +[tool.mypy] +exclude = [ + "docs", +] +explicit_package_bases = "True" + +[tool.coverage.run] +omit = [ + "py_template/__init*", + "*/py_template/__init*", + "test/*", + "*/test/*", +] \ No newline at end of file diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/test/__init__.py @@ -0,0 +1 @@ + diff --git a/test/test_example_module.py b/test/test_example_module.py new file mode 100644 index 0000000..151e857 --- /dev/null +++ b/test/test_example_module.py @@ -0,0 +1,19 @@ +import numpy as np + +from py_template.example_module import fibonacci, SoftwareGroup + + +def test_example_function() -> None: + """Check the Fibonacci sequence function returns correct result""" + fib = fibonacci(10) + np.testing.assert_allclose( + actual=fib, + desired=[1, 1, 2, 3, 5, 8, 13, 21, 34, 55], + rtol=1e0, + ) + + +def test_example_class() -> None: + """Check the SoftwareGroup class can be instantiated and its class method called""" + sg = SoftwareGroup(people="us", purpose="fun") + sg.long_term_goals()