diff --git a/.cruft.json b/.cruft.json new file mode 100644 index 00000000..277c0d0d --- /dev/null +++ b/.cruft.json @@ -0,0 +1,23 @@ +{ + "template": "https://github.com/zacharyburnett/stsci-package-template", + "commit": "4503bbf380a0471fc60ea92118ca384407aa2ea0", + "checkout": "modernize", + "context": { + "cookiecutter": { + "project_name": "stpipe", + "package_name": "stpipe", + "repository_url": "https://github.com/spacetelescope/stpipe", + "project_description": "Provides base classes and command-line tools for implementing calibration pipeline software.", + "manage_changelog_with_towncrier": true, + "publish_docs_to": "readthedocs.io", + "task_runner": "tox", + "python_version": ">=3.10", + "python_c_extensions": false, + "python_build_backend": "setuptools", + "crds_observatory": "none", + "_template": "https://github.com/zacharyburnett/stsci-package-template", + "_commit": "4503bbf380a0471fc60ea92118ca384407aa2ea0" + } + }, + "directory": "python-package" +} diff --git a/.github/actionlint.yml b/.github/actionlint.yml new file mode 100644 index 00000000..fca830de --- /dev/null +++ b/.github/actionlint.yml @@ -0,0 +1,5 @@ +paths: + .github/workflows/**/*.{yml,yaml}: + ignore: + - "SC2086" + - "SC2046" diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 002389e9..4aad21b7 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,20 +1,16 @@ version: 2 -updates: - - # Maintain dependencies for GitHub Actions - - package-ecosystem: "github-actions" - directory: "/" - target-branch: "main" - schedule: - interval: "weekly" - reviewers: - - "zacharyburnett" - # Maintain dependencies for pip +updates: - package-ecosystem: "pip" directory: "/" + labels: + - no-changelog-entry-needed target-branch: "main" schedule: - interval: "weekly" - reviewers: - - "zacharyburnett" + interval: "monthly" + cooldown: + default-days: 7 + groups: + actions: + patterns: + - "*" diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 00000000..2ae8a5f4 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,44 @@ +breaking-change: + - changed-files: + - any-glob-to-any-file: + - "changes/*.breaking.rst" + +feature: + - changed-files: + - any-glob-to-any-file: + - "changes/*.feature.rst" + +fix: + - changed-files: + - any-glob-to-any-file: + - "changes/*.fix.rst" + +documentation: + - changed-files: + - all-globs-to-any-file: ["*.rst", "!CHANGES.rst", "!changes/*"] + - any-glob-to-any-file: + - "docs/**/*" + - "*.md" + - ".readthedocs.yaml" + - "LICENSE" + - "changes/*.docs.rst" + +packaging: + - changed-files: + - any-glob-to-any-file: + - "pyproject.toml" + +# --------------------------------------- testing --------------------------------------- + +automation: + - changed-files: + - any-glob-to-any-file: + - ".github/**" + +testing: + - changed-files: + - any-glob-to-any-file: + - "**/tests/**" + - "**/regtest/**" + - ".github/workflows/test*.yml" + - "**/conftest.py" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 6b18972b..27e2de39 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,20 +1,24 @@ - -Resolves [JP-nnnn](https://jira.stsci.edu/browse/JP-nnnn) -Resolves [RCAL-nnnn](https://jira.stsci.edu/browse/RCAL-nnnn) + + + - -Closes # + + - -This PR addresses ... + + +## Description + +This change ... + + - ## Tasks + - [ ] update or add relevant tests - [ ] update relevant docstrings and / or `docs/` page -- [ ] Does this PR change any API used downstream? (if not, label with `no-changelog-entry-needed`) - - [ ] write news fragment(s) in `changes/`: `echo "changed something" > changes/..rst` (see [changelog readme](https://github.com/spacetelescope/stpipe/blob/main/changes/README.rst) for instructions) +- [ ] If this change affects user-facing code or public API, add news fragment file(s) to `changes/` (see [the changelog instructions](https://github.com/spacetelescope/stpipe/blob/main/changes/README.md)). + Otherwise, add the `no-changelog-entry-needed` label. - [ ] run regression tests with this branch installed (`stpipe@git+https://github.com//stpipe.git@`) - [ ] [`jwst` regression test](https://github.com/spacetelescope/RegressionTests/actions/workflows/jwst.yml) - [ ] [`romancal` regression test](https://github.com/spacetelescope/RegressionTests/actions/workflows/romancal.yml) diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 00000000..d60fd108 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,17 @@ +changelog: + categories: + - title: Breaking Changes + labels: + - breaking-change + - title: New Features + labels: + - feature + - title: Fixes + labels: + - fix + - title: Documentation Changes + labels: + - documentation + - title: Other Changes + labels: + - "*" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 80c96ed9..0eb18b05 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,15 +1,52 @@ name: build on: + pull_request: release: types: [released] - pull_request: workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: build: - uses: OpenAstronomy/github-actions-workflows/.github/workflows/publish_pure_python.yml@v2 - with: - upload_to_pypi: ${{ (github.event_name == 'release') && (github.event.action == 'released') }} - secrets: - pypi_token: ${{ secrets.PYPI_PASSWORD_STSCI_MAINTAINER }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-tags: true + persist-credentials: false + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: "3" + - run: pip install build + - run: python -m build --sdist --wheel + - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: dist + path: ./dist/ + publish: + if: (github.event_name == 'release') && (github.event.action == 'released') + needs: [build] + runs-on: ubuntu-latest + permissions: + id-token: write + attestations: write + # Requires environment protection rules in GitHub Settings: + # Settings > Environments > pypi > Add required reviewers + environment: + name: pypi + url: https://pypi.org/p/stpipe + steps: + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + with: + pattern: dist* + path: dist/ + merge-multiple: true + - uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3.2.0 + with: + subject-path: "dist/*" + # To upload to PyPI without a token, add this workflow file as a Trusted Publisher in the project settings on the PyPI website + - uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 00bc1625..cd1fbb48 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -10,30 +10,21 @@ on: - reopened concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: - changelog-check: + check: if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-changelog-entry-needed') }} runs-on: ubuntu-latest steps: - - uses: actions/setup-python@v6 - with: - python-version: 3.12 - uses: actions/checkout@v6 with: - fetch-depth: 0 - - run: pip install . - - run: pip install towncrier - - run: towncrier check - - run: towncrier build --draft | grep -P '#${{ github.event.number }}' - changelog-prevent-manual-edit: - if: ${{ !contains(github.event.pull_request.labels.*.name, 'allow-manual-changelog-edit') }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 + persist-credentials: false + - uses: actions/setup-python@v6 with: - fetch-depth: 0 - - name: prevent direct changes to `CHANGES.rst` (write a towncrier fragment in `changes/` instead; you can override this with the `allow-manual-changelog-edit` label) - run: git diff HEAD ${{ github.event.pull_request.base.sha }} --no-patch --exit-code CHANGES.rst + python-version: "3" + - run: pip install . towncrier + - run: towncrier check + - name: search for news fragment matching pull request number + run: towncrier build --draft | grep -P '#${{ github.event.number }}' diff --git a/.github/workflows/label.yml b/.github/workflows/label.yml new file mode 100644 index 00000000..7d70a9a9 --- /dev/null +++ b/.github/workflows/label.yml @@ -0,0 +1,15 @@ +on: + pull_request_target: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + label: + runs-on: ubuntu-latest + steps: + - uses: actions/labeler@v6 + if: github.event_name == 'pull_request_target' || github.event_name == 'pull_request' + with: + repo-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e177f8cf..e29575c5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,20 +1,21 @@ name: tests on: + pull_request: + types: + - opened + - synchronize + - reopened + - ready_for_review push: branches: - main - tags: - - "*" - pull_request: - schedule: - # Weekly Monday 9AM build - # * is a special character in YAML so you have to quote this string - - cron: "0 9 * * 1" workflow_dispatch: +permissions: {} + concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: @@ -23,15 +24,17 @@ jobs: with: envs: | - linux: py310-oldestdeps-cov-xdist - - linux: py310-xdist - - linux: py311-xdist - linux: py311-downstreamdeps-cov-xdist coverage: 'codecov' - linux: py312-xdist-nolegacypath - - linux: py313-xdist - linux: py314-cov-xdist coverage: 'codecov' - macos: py314-xdist + fill: true + fill_platforms: linux + fill_factors: xdist + artifact-path: | + results.xml test_downstream: uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@v2 with: @@ -42,4 +45,4 @@ jobs: envs: | - linux: py313-jwst-cov - linux: py313-romancal-cov - coverage: codecov + coverage: github diff --git a/.github/workflows/tests_extra.yml b/.github/workflows/tests_extra.yml index 5971eea2..dc380ac1 100644 --- a/.github/workflows/tests_extra.yml +++ b/.github/workflows/tests_extra.yml @@ -2,30 +2,34 @@ name: extra tests on: schedule: - # Weekly Monday 6AM build - - cron: "0 0 * * 1" + - cron: "6 0 * * 1" # every Monday at 6a pull_request: - # We also want this workflow triggered if the `Weekly CI` label is - # added or present when PR is updated types: - opened + - synchronize - reopened + - ready_for_review - labeled - unlabeled - - synchronize push: tags: - "*" workflow_dispatch: +permissions: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: test: uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@v2 if: (github.repository == 'spacetelescope/stpipe' && (github.event_name == 'schedule' || github.event_name == 'push' || github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'run extra tests'))) with: envs: | - - macos: py311-xdist - - macos: py312-xdist - - macos: py313-xdist - linux: py314-devdeps-xdist - macos: py314-devdeps-xdist + fill: true + fill_platforms: macos + fill_factors: xdist diff --git a/.github/workflows/update.yml b/.github/workflows/update.yml new file mode 100644 index 00000000..9effbb68 --- /dev/null +++ b/.github/workflows/update.yml @@ -0,0 +1,57 @@ +on: + schedule: + - cron: "0 2 * * 1" + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +permissions: + contents: write + pull-requests: write + +jobs: + update: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: "3" + - name: Check for updates to upstream template + id: upstream-updates + run: | + CHANGES=0 + if [ -f .cruft.json ]; then + if ! cruft check; then + CHANGES=1 + fi + else + echo "No .cruft.json file" + fi + + echo "available=$CHANGES" >> "$GITHUB_OUTPUT" + - if: steps.upstream-updates.outputs.available + name: Apply updates from upstream template + run: | + uvx cruft update --skip-apply-ask --refresh-private-variables + git restore --staged . + - if: steps.upstream-updates.outputs.available + uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} + add-paths: . + commit-message: "chore: use Cruft to apply changes from the upstream template" + branch: cruft/update + delete-branch: true + branch-suffix: timestamp + title: | + [CRUFT] Apply updates from upstream template + body: | + [Cruft](https://cruft.github.io/cruft/) has detected updates to the upstream Cookiecutter template. + + > [!TIP] + > To skip these updates, modify this PR to increment `.cruft.json` only. diff --git a/.gitignore b/.gitignore index 133ed991..c0ae6efb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +### Python ### # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] @@ -24,16 +25,8 @@ share/python-wheels/ *.egg-info/ .installed.cfg *.egg -uv.lock -.python-version MANIFEST -# 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 @@ -57,26 +50,9 @@ cover/ *.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 @@ -87,24 +63,17 @@ 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 +.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 - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow -__pypackages__/ +Pipfile.lock -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py +# uv +uv.lock # Environments .env @@ -119,12 +88,6 @@ venv.bak/ .spyderproject .spyproject -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - # mypy .mypy_cache/ .dmypy.json @@ -139,7 +102,15 @@ dmypy.json # Cython debug symbols cython_debug/ -# setuptools_scm generated version file -_version.py +# 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/ + +# ruff +.ruff_cache/ -docs/source/api +# LSP config files +pyrightconfig.json diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0026eab1..96e7df28 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,4 +1,5 @@ ci: + autofix_prs: false autoupdate_schedule: monthly exclude: ".*\\.asdf$" @@ -21,6 +22,46 @@ repos: - id: detect-private-key - id: end-of-file-fixer - id: trailing-whitespace + # validate change log and fragments in `changes/` + - repo: https://github.com/twisted/towncrier + rev: 25.8.0 + hooks: + - id: towncrier-check + # format YAML and Markdown + - repo: https://github.com/rbubley/mirrors-prettier + rev: v3.6.2 + hooks: + - id: prettier + types_or: + - yaml + - markdown + # lint YAML + - repo: https://github.com/adrienverge/yamllint.git + rev: v1.29.0 + hooks: + - id: yamllint + args: [-d relaxed] + # lint GitHub Actions workflows + - repo: https://github.com/rhysd/actionlint + rev: v1.7.11 + hooks: + - id: actionlint + # format and lint TOML + - repo: https://github.com/tombi-toml/tombi-pre-commit + rev: v0.9.17 + hooks: + - id: tombi-format + - id: tombi-lint + # spell-checking + - repo: https://github.com/codespell-project/codespell + rev: v2.4.1 + hooks: + - id: codespell + args: + - --write-changes + - --summary + additional_dependencies: + - tomli # simple text searches for common issues - repo: https://github.com/pre-commit/pygrep-hooks rev: v1.10.0 @@ -30,48 +71,33 @@ repos: - id: text-unicode-replacement-char # lint and format - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.9 + rev: v0.15.4 hooks: + - id: ruff-format - id: ruff-check args: - --fix - --show-fixes - - id: ruff-format # # type checking # - repo: https://github.com/pre-commit/mirrors-mypy - # rev: v1.17.0 + # rev: v1.19.1 # hooks: # - id: mypy - # spell-checking - - repo: https://github.com/codespell-project/codespell - rev: v2.4.2 - hooks: - - id: codespell - args: - - --write-changes - - --summary - additional_dependencies: - - tomli - # validate change log and fragments in `changes/` - - repo: https://github.com/twisted/towncrier - rev: 25.8.0 - hooks: - - id: towncrier-check - # # format YAML and Markdown - # - repo: https://github.com/rbubley/mirrors-prettier - # rev: v3.6.2 - # hooks: - # - id: prettier - # types_or: - # - yaml - # - markdown - # # format TOML - # - repo: https://github.com/ComPWA/taplo-pre-commit - # rev: v0.9.3 - # hooks: - # - id: taplo-format # # validate numpy-style docstrings # - repo: https://github.com/numpy/numpydoc - # rev: v1.9.0 + # rev: v1.10.0 # hooks: # - id: numpydoc-validation + # exclude: | + # (?x)^( + # jwst/tests/.* | + # docs/.* | + # jwst/regtest/test_.* | + # .*/tests/test_.* + # )$ + # C style + - repo: https://github.com/pre-commit/mirrors-clang-format + rev: v22.1.0 + hooks: + - id: clang-format + types_or: [c] diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 7e365e32..a5a658ac 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,35 +1,26 @@ -# .readthedocs.yaml -# Read the Docs configuration file -# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details +# yaml-language-server: $schema=https://raw.githubusercontent.com/readthedocs/readthedocs.org/refs/heads/main/readthedocs/rtd_tests/fixtures/spec/v2/schema.json +# https://docs.readthedocs.io/en/stable/config-file/v2.html -# Required version: 2 build: - os: ubuntu-24.04 + os: ubuntu-lts-latest tools: - python: mambaforge-latest + python: latest + apt_packages: + - graphviz jobs: post_checkout: - git fetch --unshallow || true - git config remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*' || true - git fetch --all --tags || true pre_install: - - git update-index --assume-unchanged docs/rtd_environment.yaml docs/source/conf.py + - git update-index --assume-unchanged docs/source/conf.py post_install: + - pip install towncrier + pre_build: - git describe --exact-match || towncrier build --keep -conda: - environment: docs/rtd_environment.yaml - -# Build documentation in the docs/ directory with Sphinx -sphinx: - builder: html - configuration: docs/source/conf.py - fail_on_warning: true - -# Install regular dependencies. -# Then, install special pinning for RTD. python: install: - method: pip @@ -37,7 +28,11 @@ python: extra_requirements: - docs -# Optionally build your docs in additional formats such as PDF and ePub +sphinx: + builder: html + configuration: docs/conf.py + fail_on_warning: true + formats: - htmlzip - pdf diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 8d726b0f..07a1c677 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,6 +1,6 @@ -# Spacetelescope Open Source Code of Conduct +# STScI Open Source Code of Conduct -We expect all "spacetelescope" organization projects to adopt a code of conduct that ensures a productive, respectful environment for all open source contributors and participants. We are committed to providing a strong and enforced code of conduct and expect everyone in our community to follow these guidelines when interacting with others in all forums. Our goal is to keep ours a positive, inclusive, successful, and growing community. The community of participants in open source Astronomy projects is made up of members from around the globe with a diverse set of skills, personalities, and experiences. It is through these differences that our community experiences success and continued growth. +We expect all `spacetelescope` organization projects to adopt a code of conduct that ensures a productive, respectful environment for all open source contributors and participants. We are committed to providing a strong and enforced code of conduct and expect everyone in our community to follow these guidelines when interacting with others in all forums. Our goal is to keep ours a positive, inclusive, successful, and growing community. The community of participants in open source Astronomy projects is made up of members from around the globe with a diverse set of skills, personalities, and experiences. It is through these differences that our community experiences success and continued growth. As members of the community, @@ -18,6 +18,8 @@ As members of the community, This code of conduct applies to all community situations online and offline, including mailing lists, forums, social media, conferences, meetings, associated social events, and one-to-one interactions. -Parts of this code of conduct have been adapted from the Astropy and Numfocus codes of conduct. -http://www.astropy.org/code_of_conduct.html -https://www.numfocus.org/about/code-of-conduct/ +> [!NOTE] +> Parts of this code of conduct were adapted from the Astropy and Numfocus codes of conduct. +> +> - https://www.astropy.org/code_of_conduct.html +> - https://www.numfocus.org/about/code-of-conduct/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2fb585dc..731b17fb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,177 @@ -Please open a new issue or new pull request for bugs, feedback, or new features you would like to see. If there is an issue you would like to work on, please leave a comment and we will be happy to assist. New contributions and contributors are very welcome! +# Contributing to stpipe -The main development work is done on the "main" branch. The "stable" branch is protected and used for official releases. The rest of the branches are for release maintenance and should not be used normally. Unless otherwise told by a maintainer, pull requests should be made and submitted to the "main" branch. +`stpipe` is an open source package written in Python. +The source code is available at https://github.com/spacetelescope/stpipe. +New contributions and contributors are very welcome! +Do not hesitate to reach out to the package maintainers if you are new to open-source development or if you have any questions or concerns. +We only ask that all contributors adhere to the [Space Telescope Code of Conduct](./CODE_OF_CONDUCT.md). -New to GitHub or open source projects? If you are unsure about where to start or haven't used GitHub before, please feel free to contact the package maintainers. +## Reporting bugs / requesting a new feature -Feedback and feature requests? Is there something missing you would like to see? Please open an issue or send an email to the maintainers. This package follows the Spacetelescope [Code of Conduct](CODE_OF_CONDUCT.md) strives to provide a welcoming community to all of our users and contributors. +If you have encountered a bug, or wish to request a new feature, +[open an issue](https://github.com/spacetelescope/stpipe/issues). + +## Suggesting code changes / contributions + +> [!TIP] +> If you are new to GitHub, to `git`, or to version-control systems in general, refer to the [GitHub tutorial](https://docs.github.com/en/get-started/git-basics/set-up-git) and / or to the [`git` reference manual](https://git-scm.com/docs). + +To suggest a specific code change, or to contribute new code: + +1. [Fork this repository](https://github.com/spacetelescope/stpipe/fork). +2. Clone your fork to your local machine: + + ```shell + git clone https://github.com/YOUR_USERNAME/stpipe + cd stpipe/ + ``` + +3. Add the `upstream` repository, as a remote, to your local clone: + ```shell + git remote add upstream https://github.com/spacetelescope/stpipe + ``` + +> [!TIP] +> When making changes, create a new "branch" for each new feature or bug fix. +> We recommend naming your new branch something like `feature/cool_new_feature`, `fix/thing_that_was_fixed`, `docs/updated_description_of_feature`, etc: +> +> ```shell +> git fetch upstream --tags +> git checkout upstream/main -b fix/that_annoying_bug +> ``` + +4. Install `pre-commit` to automatically check your changes for formatting issues: + ```shell + pip install pre-commit + pre-commit install + ``` + +> [!TIP] +> To run `pre-commit` checks manually, do `pre-commit run --all`. + +5. [Install `stpipe` to your development environment.](#creating-a-development-environment) +6. Make your changes using your editor of choice. +7. Commit and push your changes to your fork as a new branch: + ```shell + git add changed_file.py + git commit -m "description of changes" + git push + ``` + The [`git` reference manual](https://git-scm.com/docs) has details on what these commands do. +8. [Open a new Pull Request](https://github.com/spacetelescope/stpipe/pulls) requesting that your changes be merged into the `main` branch of this repository. +9. Ensure that your change passes automated testing. +10. Complete the items in the **Tasks** checklist (created when you open the pull request) to the best of your ability. + +Once your pull request is created, it will need to be reviewed and approved by the code maintainer team. +They may require changes from you before your code can be merged, +in which case go back and make those changes, run tests again, and push the changes to the branch you made earlier. + +## Keeping your development branch current with `main` + +As `stpipe` is constantly evolving, you will often encounter the situation where you've made changes to your branch, but in that time there are new commits on `upstream/main` from other developers. +Incorporate those changes into your branch, either automatically with the button on the GitHub pull request webpage, or manually with `git rebase`. + +### Incorporate upstream changes automatically with button on GitHub pull request page + +Usually, GitHub can rebase a branch automatically. +If you see "**This branch is out-of-date with the base branch**", you will have the option to "**Update with merge commit**" or "**Update with rebase**". Updating with a merge commit is usually safer. + +However, if the changes to `main` touch the same lines as your changes, you will see "**This branch has conflicts that must be resolved**". You will need to manually resolve these conflicts yourself; follow the steps described on the page. + +### Incorporate upstream changes manually with `git rebase` + +Rebase your current branch onto `upstream/main` to apply any new changes on top of yours: + +```shell +git fetch --all +git rebase -i upstream/main +``` + +For more information on how to use `git rebase`, see [the `git rebase` documentation](https://git-scm.com/docs/git-rebase) or [Atlassian's tutorial on rebasing](https://www.atlassian.com/git/tutorials/rewriting-history/git-rebase). + +Once you've completed your rebase, you will need to "force push" your branch to **overwrite your branch on GitHub**: + +```shell +git push -u origin -f feature/cool_new_feature +``` + +## Creating a development environment + +When developing `stpipe` (or any other Python package), you should install the package locally to a development environment. + +> [!TIP] +> Python "environments" are isolated Python installations, confined to a single directory, where you can install packages, dependencies, and tools without cluttering your system Python libraries. + +You can create a development environment with `mamba` / `conda`: + +```shell +mamba create -n stpipe_dev_env python=3.13 +mamba activate stpipe_dev_env +pip install -e . +hx . +``` + +Breaking down what these lines do: + +1. Create a new empty environment called `stpipe_dev_env`: + ```shell + mamba create -n stpipe_dev_env python=3.13 + ``` +2. "Activate" the environment (change shell variables in the current session to point to the isolated Python installation): + ```shell + mamba activate stpipe_dev_env + ``` +3. Install the local package (`stpipe`) to your environment in "editable mode", so that any code changes will be instantly reflected in the installed package (useful for testing): + ```shell + pip install -e . + ``` +4. Run your editor of choice (in this example I use Helix `hx`): + ```shell + hx . + ``` + +## Making simultaneous changes to `stpipe` and one of its dependencies + +If you need to make a change in `stpipe` that requires a simultaneous change to one of its dependencies, also install that dependency from your local machine [to your development environment](#creating-a-development-environment). + +> [!TIP] +> It might be easier to use a separate Python environment (`mamba` / `conda`, `virtualenv`, `uv`, etc.) for this work. + +## Code style + +We use `pre-commit` to enforce uniform code style and standards. + +```shell +pip install pre-commit +pre-commit run +``` + +You can also install `pre-commit` locally, to run checks before every `git commit` action: + +```shell +pre-commit install +``` + +The full configuration for `pre-commit` checks can be found in `.pre-commit-config.yaml`. + +### PEP8 compliance + +Code style generally conforms to [PEP8](https://peps.python.org/pep-0008/), +enforced using [`ruff`](https://docs.astral.sh/ruff/). +`ruff` will automatically pick up the appropriate configuration and perform only the checks that are turned on for our repository. + +### Numpy docstring style + +Docstrings conform to the [Numpy style guide](https://numpydoc.readthedocs.io/en/latest/format.html), enforced with [`numpydoc-validation`](https://numpydoc.readthedocs.io/en/latest/validation.html). + +### Spell checking + +We use [Codespell](https://github.com/codespell-project/codespell) to check for common misspellings in both our codebase and documentation. + +### PEP-compliant type hints + +Type hints are _not_ required for contributions. If type hints are used, though, their compliance with [PEP-484](https://peps.python.org/pep-0484/) is enforced with [`mypy`](https://mypy.readthedocs.io/en/stable/index.html). + +## Writing and maintaining documentation + +See [`docs/README.md`](./docs/README.md) for instructions. diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 00000000..f0635edd --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,30 @@ +BSD 3-Clause License + +Copyright (c) 2026 Association of Universities for Research in Astronomy (AURA) + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. 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. + + 3. The name of AURA and its representatives may not be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY AURA ``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 AURA 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/README.md b/README.md index 4bc86c93..129eb2da 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,17 @@ + + STScI Logo + STScI Name + + # stpipe -[![CI](https://github.com/spacetelescope/stpipe/actions/workflows/tests.yml/badge.svg)](https://github.com/spacetelescope/stpipe/actions/workflows/tests.yml) -[![codecov](https://codecov.io/gh/spacetelescope/stpipe/branch/main/graph/badge.svg?token=Mm9I0X1o4X)](https://codecov.io/gh/spacetelescope/stpipe) +[![build](https://github.com/spacetelescope/stpipe/actions/workflows/build.yml/badge.svg)](https://github.com/spacetelescope/stpipe/actions/workflows/build.yml) +[![tests](https://github.com/spacetelescope/stpipe/actions/workflows/tests.yml/badge.svg)](https://github.com/spacetelescope/stpipe/actions/workflows/tests.yml) +[![ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) +[![Powered by STScI](https://img.shields.io/badge/powered%20by-STScI-blue.svg?colorA=707170&colorB=3e8ddd&style=flat)](https://www.stsci.edu) +[![ReadTheDocs](https://readthedocs.org/projects/stpipe/badge/?version=latest)](https://stpipe.readthedocs.io/en/latest/?badge=latest) Provides base classes and command-line tools for implementing calibration pipeline software. > [!NOTE] -> Linux and MacOS platforms are tested and supported. Windows is not currently supported. +> Linux and MacOS platforms are tested and supported. Windows is not currently supported. diff --git a/TESTING.md b/TESTING.md new file mode 100644 index 00000000..8c18f8e2 --- /dev/null +++ b/TESTING.md @@ -0,0 +1,24 @@ +# Running tests for stpipe + +`stpipe` has several test suites to ensure that functionality remains consistent and does not break when code changes. +In order for a change you make to the code to be accepted and merged, that change must pass existing tests, as well as any new tests you write that cover new functionality. + +`stpipe` uses `pytest` to define and run tests. To install `pytest` and other required testing tools to your [development environment](./CONTRIBUTING.md#creating-a-development-environment), install `stpipe` with the `test` extra: + +```shell +pip install -e .[test] +``` + +To run tests, simply run `pytest`: + +```shell +pytest +``` + +`pytest` recursively searches the given directory (by default `.`) for any files with a name like `test_*.py`, and runs all functions it finds that have a name like `test_*`. + +See the [`pytest` documentation](https://docs.pytest.org) for more instructions on using `pytest`. + +> [!TIP] +> You can control where test results are written by adding `--basetemp=` to your `pytest` command. +> `pytest` will wipe this directory clean for each test session, so make sure it is a scratch area. diff --git a/changes/README.md b/changes/README.md new file mode 100644 index 00000000..f570a308 --- /dev/null +++ b/changes/README.md @@ -0,0 +1,22 @@ +# Writing News Fragments for the Changelog + +This `changes/` directory contains "news fragments": small reStructuredText (`.rst`) files describing a change in a few sentences. +When making a release, run `towncrier build --version ` to consume existing fragments in `changes/` +and insert them as a full changelog entry at the top of `CHANGES.rst` for the released version. + +News fragment filenames consist of the pull request number and the changelog category (see below). +A single change can have more than one news fragment, if it spans multiple categories. + +## Change Log Categories + +Make a news fragment for every relevant category affected by your change. + +- `.breaking.rst`: Add this fragment if your change **breaks public API**, describing what the user needs to change +- `.feature.rst` +- `.fix.rst` +- `.docs.rst` +- `.other.rst` + +> [!NOTE] +> This README was adapted from a similar one in Astropy (under the terms of the BSD license), +> which was in turn adapted from Numpy (MIT license). diff --git a/changes/README.rst b/changes/README.rst deleted file mode 100644 index e9f15f92..00000000 --- a/changes/README.rst +++ /dev/null @@ -1,16 +0,0 @@ -Writing news fragments for the change log -######################################### - -This ``changes/`` directory contains "news fragments": small reStructuredText (`.rst`) files describing a change in a few sentences. -When making a release, run ``towncrier build --version `` to consume existing fragments in ``changes/`` and insert them as a full change log entry at the top of ``CHANGES.rst`` for the released version. - -News fragment filenames consist of the pull request number and the change log category (see below). A single change can have more than one news fragment. - -Change log categories -********************* - -- ``.feature.rst``: new feature -- ``.bugfix.rst``: fixes an issue -- ``.doc.rst``: documentation change -- ``.removal.rst``: deprecation or removal of public API -- ``.misc.rst``: infrastructure or miscellaneous change diff --git a/docs/Makefile b/docs/Makefile index d0c3cbf1..7dcd4cf3 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,20 +1,162 @@ -# Minimal makefile for Sphinx documentation +# 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 = source -BUILDDIR = build +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build +APIDIR = api + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +#This is needed with git because git doesn't create a dir if it's empty +$(shell [ -d "_static" ] || mkdir -p _static) + -# Put it first so that "make" without argument is like "make help". help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + -rm -rf $(APIDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/wfc3tools.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/wfc3tools.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/wfc3tools" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/wfc3tools" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." -.PHONY: help Makefile +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." -# 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) +livehtml: + sphinx-autobuild -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..9a5be87b --- /dev/null +++ b/docs/README.md @@ -0,0 +1,17 @@ +# Writing and maintaining documentation + +Documentation for `stpipe` is written in [Sphinx reStructuredText (`.rst`)](https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html) +in this `docs/` directory, and is hosted online athttps://stpipe.readthedocs.io### Building documentation locally +ReadTheDocs will automatically build documentation for your branch when you push a commit to a pull request on this GitHub repository, and host a temporary build with a visual diff. +However, it is also good practice to build the docs locally if you are editing them, to reduce frustration from small errors. + +To build the docs locally (assuming you have [set up your environment as described in `CONTRIBUTING.md`](../CONTRIBUTING.md#creating-a-development-environment)): + +```shell +cd docs/ +make clean +make html +``` + +The docs will build to `docs/_build/html/`. +Open `docs/_build/html/index.html` to view the pages in your browser. diff --git a/docs/assets/stsci_logo.png b/docs/assets/stsci_logo.png new file mode 100644 index 00000000..3715ca65 Binary files /dev/null and b/docs/assets/stsci_logo.png differ diff --git a/docs/assets/stsci_logo_with_name.png b/docs/assets/stsci_logo_with_name.png new file mode 100644 index 00000000..4047d78e Binary files /dev/null and b/docs/assets/stsci_logo_with_name.png differ diff --git a/docs/assets/stsci_name.png b/docs/assets/stsci_name.png new file mode 100644 index 00000000..fa03b6c5 Binary files /dev/null and b/docs/assets/stsci_name.png differ diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..81665ae9 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,167 @@ +# Sphinx configuration +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +import importlib +import tomllib +from datetime import datetime +from pathlib import Path + +from sphinx.ext.autodoc import AttributeDocumenter + +from stpipe import Step + + +class StepSpecDocumenter(AttributeDocumenter): + def should_suppress_value_header(self): + if self.name == "spec" and issubclass(self.parent, Step): + # if this attribute is named "spec" and belongs to a "Step" + # don't show the value, it will be formatted in add_context below + return True + return super().should_suppress_value_header() + + def add_content(self, more_content): + super().add_content(more_content) + if self.name != "spec" or not issubclass(self.parent, Step): + return + if not self.object.strip(): + return + + # format the long "Step.spec" string to improve readability + source_name = self.get_sourcename() + self.add_line("::", source_name, 0) + self.add_line(" ", source_name, 1) + txt = "\n".join((l.strip() for l in self.object.strip().splitlines())) + self.add_line(f" {txt}", source_name, 2) + + +def setup(app): + # add a custom AttributeDocumenter subclass to handle Step.spec formatting + def register_documenter(app, config): + app.add_autodocumenter(StepSpecDocumenter, True) + + # register it with a high priority so it behaves with the built-in autodoc + app.connect("config-inited", register_documenter, priority=9000) + + +# -- Project information ----------------------------------------------------- + +with open( + Path(__file__).parent.parent / "pyproject.toml", "rb" +) as project_metadata_file: + project_metadata = tomllib.load(project_metadata_file)["project"] + +project = project_metadata["name"] +author = project_metadata["authors"][0]["name"] +copyright = f"{datetime.today().year}, {author}" + +package = importlib.import_module(project_metadata["name"]) +try: + release = package.__version__ + version = package.__version__.split("-", 1)[0] +except AttributeError: + release = "dev" + version = "dev" + +# -- General configuration --------------------------------------------------- + +extensions = [ + "sphinx.ext.autodoc", + "sphinx_automodapi.automodapi", + "numpydoc", + "sphinx.ext.intersphinx", +] + +intersphinx_mapping = { + "asdf": ("https://asdf.readthedocs.io/en/stable/", None), + "astropy": ("https://docs.astropy.org/en/stable/", None), + "drizzle": ("https://spacetelescope-drizzle.readthedocs.io/en/latest/", None), + "gwcs": ("https://gwcs.readthedocs.io/en/stable/", None), + "matplotlib": ("https://matplotlib.org/", None), + "numpy": ("https://numpy.org/devdocs", None), + "photutils": ("https://photutils.readthedocs.io/en/stable/", None), + "python": ("https://docs.python.org/3/", None), + "requests": ("https://requests.readthedocs.io/en/latest/", None), + "scipy": ("https://scipy.github.io/devdocs", None), + "stcal": ("https://stcal.readthedocs.io/en/latest/", None), + "stdatamodels": ("https://stdatamodels.readthedocs.io/en/latest/", None), + "stpipe": ("https://stpipe.readthedocs.io/en/latest/", None), + "synphot": ("https://synphot.readthedocs.io/en/latest/", None), + "tweakwcs": ("https://tweakwcs.readthedocs.io/en/latest/", None), +} +intersphinx_disabled_domains = ["std"] + +templates_path = ["_templates"] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + +# reST default role (used for this markup: `text`) to use for all documents +default_role = "obj" + +# -- Options for HTML output ------------------------------------------------- + +html_theme = "sphinx_rtd_theme" +html_theme_options = { + "collapse_navigation": True, + "sticky_navigation": False, + # "nosidebar": "false", + # "sidebarbgcolor": "#4db8ff", + # "sidebartextcolor": "black", + # "sidebarlinkcolor": "black", + # "headbgcolor": "white", +} +html_logo = "_static/stsci_pri_combo_mark_white.png" +html_static_path = ["_static"] +html_css_files = ["custom.css"] +html_last_updated_fmt = "%b %d, %Y" +html_sidebars = {"**": ["globaltoc.html", "relations.html", "searchbox.html"]} +html_domain_indices = True +html_use_index = True + +# -- Options for EPUB output ------------------------------------------------- +epub_show_urls = "footnote" + +# -- Options for extensions + +# Don't show summaries of the members in each class along with the class' docstring +numpydoc_show_class_members = False + +autosummary_generate = True + +automodapi_toctreedirnm = "api" + +# Class documentation should contain *both* the class docstring and the __init__ docstring +autoclass_content = "both" + +# Render inheritance diagrams in SVG +graphviz_output_format = "svg" + +graphviz_dot_args = [ + "-Nfontsize=10", + "-Nfontname=Helvetica Neue, Helvetica, Arial, sans-serif", + "-Efontsize=10", + "-Efontname=Helvetica Neue, Helvetica, Arial, sans-serif", + "-Gfontsize=10", + "-Gfontname=Helvetica Neue, Helvetica, Arial, sans-serif", +] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = "default" + +# -- Options for linkcheck ---------------------------------------------- + +# linkcheck +linkcheck_retry = 5 +linkcheck_ignore = [ + "http://stsci.edu/schemas/fits-schema/", # Old schema from CHANGES.rst + "https://outerspace.stsci.edu", # CI blocked by service provider + "https://jira.stsci.edu/", # Internal access only + r"https://.*\.readthedocs\.io", # 429 Client Error: Too Many Requests + "https://doi.org", # CI blocked by service provider (timeout) +] +linkcheck_timeout = 180 +linkcheck_anchors = False +linkcheck_report_timeouts_as_broken = True +linkcheck_allow_unauthorized = False + +# Enable nitpicky mode - which ensures that all references in the docs +# resolve. +nitpicky = True diff --git a/docs/source/index.rst b/docs/index.rst similarity index 100% rename from docs/source/index.rst rename to docs/index.rst diff --git a/docs/rtd_environment.yaml b/docs/rtd_environment.yaml deleted file mode 100644 index 7f2406d4..00000000 --- a/docs/rtd_environment.yaml +++ /dev/null @@ -1,7 +0,0 @@ -channels: - - conda-forge -dependencies: - - python=3.13 - - pip - - graphviz - - towncrier diff --git a/docs/source/_static/stsci_pri_combo_mark_white.png b/docs/source/_static/stsci_pri_combo_mark_white.png deleted file mode 100644 index 8fc1f5af..00000000 Binary files a/docs/source/_static/stsci_pri_combo_mark_white.png and /dev/null differ diff --git a/docs/source/conf.py b/docs/source/conf.py deleted file mode 100644 index 3e75c188..00000000 --- a/docs/source/conf.py +++ /dev/null @@ -1,95 +0,0 @@ -import importlib -import sys -from datetime import datetime -from pathlib import Path - -if sys.version_info < (3, 11): - import tomli as tomllib -else: - import tomllib - -from sphinx.ext.autodoc import AttributeDocumenter - -from stpipe import Step - - -class StepSpecDocumenter(AttributeDocumenter): - def should_suppress_value_header(self): - if self.name == "spec" and issubclass(self.parent, Step): - # if this attribute is named "spec" and belongs to a "Step" - # don't show the value, it will be formatted in add_context below - return True - return super().should_suppress_value_header() - - def add_content(self, more_content): - super().add_content(more_content) - if self.name != "spec" or not issubclass(self.parent, Step): - return - if not self.object.strip(): - return - - # format the long "Step.spec" string to improve readability - source_name = self.get_sourcename() - self.add_line("::", source_name, 0) - self.add_line(" ", source_name, 1) - txt = "\n".join((l.strip() for l in self.object.strip().splitlines())) - self.add_line(f" {txt}", source_name, 2) - - -def setup(app): - # add a custom AttributeDocumenter subclass to handle Step.spec formatting - def register_documenter(app, config): - app.add_autodocumenter(StepSpecDocumenter, True) - # register it with a high priority so it behaves with the built-in autodoc - app.connect("config-inited", register_documenter, priority=9000) - - -REPO_ROOT = Path(__file__).parent.parent.parent - -# Read the package's `pyproject.toml` so that we can use relevant -# values here: -with open(REPO_ROOT / "pyproject.toml", "rb") as configuration_file: - conf = tomllib.load(configuration_file) -setup_metadata = conf["project"] - -project = setup_metadata["name"] -primary_author = setup_metadata["authors"][0] -author = primary_author["name"] -copyright = f"{datetime.now().year}, {author}" - -package = importlib.import_module(project) -version = package.__version__.split("-", 1)[0] -release = package.__version__ - -extensions = [ - "sphinx.ext.autodoc", - "sphinx_automodapi.automodapi", - "numpydoc", - "sphinx.ext.intersphinx", -] - -autosummary_generate = True -numpydoc_show_class_members = False -autoclass_content = "both" - -html_theme = "sphinx_rtd_theme" -html_logo = "_static/stsci_pri_combo_mark_white.png" -html_theme_options = { - "collapse_navigation": True, -} -html_domain_indices = True -html_sidebars = {"**": ["globaltoc.html", "relations.html", "searchbox.html"]} -html_use_index = True - -# Enable nitpicky mode - which ensures that all references in the docs -# resolve. -nitpicky = True -nitpick_ignore = [] - -suppress_warnings = ["app.add_directive"] - -# Set the default role for all single backtick annotations -default_role = "obj" - -intersphinx_mapping = {} -intersphinx_mapping["python"] = ("https://docs.python.org/3", None) diff --git a/pyproject.toml b/pyproject.toml index ae22b530..35ea30be 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,150 +3,135 @@ name = "stpipe" description = "Framework for calibration pipeline software" readme = "README.md" requires-python = ">=3.10" +license-files = ["LICENSE"] authors = [ - { name = "STScI" }, + { name = "STScI" }, ] classifiers = [ - "Intended Audience :: Science/Research", - "Topic :: Scientific/Engineering :: Astronomy", - "Programming Language :: Python :: 3", + "Intended Audience :: Science/Research", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Topic :: Scientific/Engineering :: Astronomy", ] dependencies = [ - "asdf>=3", - "crds>=7.4.1.3", - "astropy>=6", - "importlib_metadata>=4.11.4", - "pyyaml>=6", -] -license-files = ["LICENSE"] -dynamic = [ - "version", -] - -[project.optional-dependencies] -docs = [ - "numpydoc", - "sphinx", - "sphinx-automodapi", - "sphinx-rtd-theme", - "tomli; python_version <\"3.11\"", -] -test = [ - "pytest >=9.0.0", - "pytest-doctestplus >=0.10.0", + "asdf>=3", + "astropy>=6", + "crds>=7.4.1.3", + "importlib_metadata>=4.11.4", + "pyyaml>=6", ] +dynamic = ["version"] [project.urls] +documentation = "https://stpipe.readthedocs.io" repository = "https://github.com/spacetelescope/stpipe" -tracker = "https://github.com/spacetelescope/stpipe/issues" - -[project.entry-points."asdf.resource_mappings"] -stpipe = "stpipe.integration:get_resource_mappings" [project.scripts] stpipe = "stpipe.cli.main:main" strun = "stpipe.cli.strun:main" -[build-system] -requires = [ - "setuptools >=61", - "setuptools_scm[toml] >=3.4", -] -build-backend = "setuptools.build_meta" +[project.entry-points."asdf.resource_mappings"] +stpipe = "stpipe.integration:get_resource_mappings" -[tool.setuptools_scm] -write_to = "src/stpipe/_version.py" +[project.optional-dependencies] +docs = [ + "numpydoc", + "sphinx", + "sphinx-automodapi", + "sphinx-rtd-theme", + "tomli; python_version <\"3.11\"", +] +test = [ + "pytest >=9.0.0", + "pytest-doctestplus >=0.10.0", +] -[tool.setuptools] -zip-safe = true +[build-system] +requires = ["setuptools>=61.2", "setuptools_scm[toml]"] +build-backend = "setuptools.build_meta" -[tool.setuptools.packages.find] -where = [ - "src", +[tool.coverage.run] +branch = true +parallel = true +source = ["src/", "tests/"] + +[tool.coverage.report] +show_missing = true +skip_covered = true +fail_under = 50 +exclude_also = [ + "if TYPE_CHECKING:", + "if typing.TYPE_CHECKING:", + "@overload", + "@typing.overload", + "class .*\\bProtocol\\):", + "@(abc\\.)?abstractmethod", + "raise NotImplementedError", + "\\.\\.\\.", +] + +[tool.cruft] +skip = ["**/__init__.py", "tests/*"] + +[tool.numpydoc_validation] +checks = [ + "all", + "EX01", # No examples section found + "SA01", # See Also section not found + "ES01", # No extended summary found + "GL08", # Object does not have a docstring. Ruff catches these, and allows more granular ignores. + "PR01", # Parameters not documented. Already caught by ruff. + "PR09", # Parameter description should finish with a period + "RT02", # First line of return should contain only the type + "RT05", # Return value description should finish with a period ] [tool.pytest] -minversion = "9" +addopts = ["--color=yes", "--strict-config", "--strict-markers", "-ra"] +filterwarnings = ["error"] log_cli_level = "INFO" -xfail_strict = true +markers = ["soctests"] +minversion = "9" +testpaths = ["tests"] doctest_plus = "enabled" doctest_rst = "enabled" text_file_format = "rst" -addopts = ["--strict-config", "--strict-markers", "-ra", "--color=yes"] -testpaths = ["tests"] -filterwarnings = ["error"] -markers = ["soctests"] [tool.ruff] src = [ - "src", - "tests", - "docs", - "setup.py", + "src", + "tests", + "docs", + "setup.py", ] line-length = 88 extend-exclude = [ - "docs", - "scripts/strun", + "docs", + "scripts/strun", ] [tool.ruff.lint] extend-select = [ - "F", # Pyflakes - "W", "E", # pycodestyle - "I", # isort - "UP", # pyupgrade - "S", # flake8-bandit - "NPY", # NumPy-specific checks (recommendations from NumPy) + "F", # Pyflakes + "W", + "E", # pycodestyle + "I", # isort + "UP", # pyupgrade + "S", # flake8-bandit + "NPY", # NumPy-specific checks (recommendations from NumPy) ] ignore = [ - "ISC001", # conflicts with ruff formatter + "ISC001", # conflicts with ruff formatter + "S607", # subprocess executable ] -[tool.ruff.lint.extend-per-file-ignores] -"tests/*.py" = [ - "S101", - "S603", - "S607", - "INP001", - "ARG001", -] -"src/stpipe/tests/*.py" = [ - "S101", +[tool.ruff.lint.per-file-ignores] +"tests/*" = [ + "S101" # usage of assert ] -"src/stpipe/cli/*.py" = [ - "T201", -] -"src/stpipe/cmdline.py" = [ - "T201", -] - -[tool.black] -line-length = 88 -force-exclude = "^/(\n (\n \\.eggs\n | \\.git\n | \\.pytest_cache\n | \\.tox\n )/\n)\n" - -[tool.codespell] -skip = "*.pdf,*.fits,*.asdf,.tox,build,./tags,.git,docs/_build" - -[tool.towncrier] -filename = "CHANGES.rst" -directory = "changes" -package = "stpipe" -title_format = "{version} ({project_date})" -ignore = [".gitkeep"] -wrap = true -issue_format = "`#{issue} `_" -[tool.towncrier.fragment.feature] -name = "New Features" - -[tool.towncrier.fragment.bugfix] -name = "Bug Fixes" - -[tool.towncrier.fragment.doc] -name = "Documentation" - -[tool.towncrier.fragment.removal] -name = "Deprecations and Removals" +[tool.setuptools] +zip-safe = true -[tool.towncrier.fragment.misc] +[tool.setuptools_scm] +version_file = "src/stpipe/_version.py" diff --git a/src/stpipe/_version.py b/src/stpipe/_version.py new file mode 100644 index 00000000..d0fba987 --- /dev/null +++ b/src/stpipe/_version.py @@ -0,0 +1,24 @@ +# file generated by vcs-versioning +# don't change, don't track in version control +from __future__ import annotations + +__all__ = [ + "__version__", + "__version_tuple__", + "version", + "version_tuple", + "__commit_id__", + "commit_id", +] + +version: str +__version__: str +__version_tuple__: tuple[int | str, ...] +version_tuple: tuple[int | str, ...] +commit_id: str | None +__commit_id__: str | None + +__version__ = version = '0.12.1.dev8+g961b552e9.d20260422' +__version_tuple__ = version_tuple = (0, 12, 1, 'dev8', 'g961b552e9.d20260422') + +__commit_id__ = commit_id = 'g961b552e9' diff --git a/towncrier.toml b/towncrier.toml new file mode 100644 index 00000000..6efd818e --- /dev/null +++ b/towncrier.toml @@ -0,0 +1,32 @@ +[tool.towncrier] +filename = "CHANGES.rst" +directory = "changes/" +ignore = ["README.md"] +title_format = "{version} ({project_date})" +issue_format = "`#{issue} `_" +wrap = true + +[[tool.towncrier.type]] +directory = "breaking" +name = "Breaking Changes" +showcontent = true + +[[tool.towncrier.type]] +directory = "feature" +name = "New Features" +showcontent = true + +[[tool.towncrier.type]] +directory = "fix" +name = "Fixes" +showcontent = true + +[[tool.towncrier.type]] +directory = "docs" +name = "Documentation Changes" +showcontent = true + +[[tool.towncrier.type]] +directory = "other" +name = "Other Changes" +showcontent = false diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 7c17ec4c..00000000 --- a/tox.ini +++ /dev/null @@ -1,67 +0,0 @@ -[tox] -envlist = - py3{,11,12}{,-warnings,-cov,-oldestdeps,-devdeps,-downstreamdeps}{-nolegacypath}{,-xdist} - py3{,11,12}{-jwst,-romancal} - -[testenv] -description = - run tests - jwst: of JWST pipeline - romancal: of Romancal pipeline - oldestdeps: with the oldest supported version of key dependencies - downstreamdeps: with the downstream packages that depend on stpipe - warnings: treating warnings as errors - cov: with coverage - xdist: using parallel processing -change_dir = - jwst,romancal: {env_tmp_dir} -allowlist_externals = - git - jwst,romancal: bash -extras = - test -deps = - xdist: pytest-xdist - cov: pytest-cov - oldestdeps: minimum_dependencies - devdeps: astropy>=0.0.dev0 - downstreamdeps: jwst - downstreamdeps: stdatamodels - downstreamdeps: roman_datamodels -pass_env = - CRDS_* - CI - romancal: WEBBPSF_PATH -set_env = - devdeps: PIP_EXTRA_INDEX_URL = https://pypi.anaconda.org/astropy/simple https://pypi.anaconda.org/scientific-python-nightly-wheels/simple - jwst,downstreamdeps: CRDS_SERVER_URL=https://jwst-crds.stsci.edu - jwst: CRDS_SERVER_URL = https://jwst-crds.stsci.edu - jwst,romancal: CRDS_PATH = {package_root}/crds_cache - jwst,romancal: CRDS_CLIENT_RETRY_COUNT = 3 - jwst,romancal: CRDS_CLIENT_RETRY_DELAY_SECONDS = 20 - romancal: CRDS_SERVER_URL=https://roman-crds.stsci.edu -package = - !cov: wheel - cov: editable -commands_pre = - oldestdeps: minimum_dependencies stpipe --filename requirements-min.txt -# this package doesn't depend on numpy directly but the old versions of dependencies -# will allow numpy 2.0 to be installed (and won't work with numpy 2.0). So we pin it. - oldestdeps: pip install numpy<2.0 - oldestdeps: pip install -r requirements-min.txt - jwst,romancal: bash -c "pip freeze -q | grep 'stpipe @' > {env_tmp_dir}/requirements.txt" - jwst: git clone https://github.com/spacetelescope/jwst.git - romancal: git clone https://github.com/spacetelescope/romancal.git - jwst: pip install -e jwst[test] - romancal: pip install -e romancal[test] - jwst,romancal: pip install -r {env_tmp_dir}/requirements.txt - pip freeze -commands = - pytest \ - warnings: -W error \ - nolegacypath: -p no:legacypath \ - xdist: -n auto \ - jwst: jwst \ - romancal: romancal \ - cov: --cov={package_root} --cov-config={package_root}/pyproject.toml --cov-report=term-missing --cov-report=xml \ - {posargs} diff --git a/tox.toml b/tox.toml new file mode 100644 index 00000000..ac579b5a --- /dev/null +++ b/tox.toml @@ -0,0 +1,112 @@ +requires = ["tox>=4.50"] +env_list = [ + { product = [ + { prefix = "py3", start = 11, stop = 14 }, + ["warnings"], + ["oldestdeps", "devdeps", "downstreamdeps"], + ["nolegacypath"], + ["cov"], + ["xdist"], + ] }, + { product = [ + { prefix = "py3", start = 11, stop = 14 }, + ["jwst", "romancal"], + ] } +] + +[env_run_base] +factors = [ + ["warnings"], + ["oldestdeps", "devdeps", "downstreamdeps", "jwst", "romancal"], + ["nolegacypath"], + ["cov"], + ["xdist"] +] +pass_env = [ + "TOXENV", + "CI", + "CODECOV_*", + "HOME", + "STRICT_VALIDATION", + "PASS_INVALID_VALUES", + "VALIDATE_ON_ASSIGNMENT", + "CRDS_*", + "TEST_BIGDATA", +] +set_env.PIP_EXTRA_INDEX_URL = { replace = "if", condition = "factor.devdeps", then = "https://pypi.anaconda.org/astropy/simple https://pypi.anaconda.org/scientific-python-nightly-wheels/simple" } +set_env.CRDS_SERVER_URL = { replace = "if", condition = "factor.romancal", then = "https://roman-crds.stsci.edu", "else" = "https://jwst-crds.stsci.edu" } +set_env.CRDS_PATH = { replace = "if", condition = "factor.jwst or factor.romancal", then = "{package_root}/crds_cache" } +set_env.CRDS_CLIENT_RETRY_COUNT = { replace = "if", condition = "factor.jwst or factor.romancal", then = "3" } +set_env.CRDS_CLIENT_RETRY_DELAY_SECONDS = { replace = "if", condition = "factor.jwst or factor.romancal", then = "20" } +deps = [ + "pytest>=9", + { replace = "if", condition = "factor.oldestdeps", then = [ + "minimum_dependencies" + ], extend = true }, + { replace = "if", condition = "factor.devdeps", then = ["astropy>=0.0.dev0"], extend = true }, + { replace = "if", condition = "factor.downstreamdeps", then = [ + "jwst", + "stdatamodels", + "roman_datamodels" + ], extend = true }, + { replace = "if", condition = "factor.cov", then = ["pytest-cov"], extend = true }, + { replace = "if", condition = "factor.xdist", then = ["pytest-xdist"], extend = true }, +] +extras = ["test"] +commands_pre = [ + { replace = "if", condition = "factor.oldestdeps", then = [ + [ + "minimum_dependencies", + "stpipe", + "--filename", + "requirements-min.txt" + ], + ["pip", "install", "numpy<2.0"], + ["pip", "install", "-r", "requirements-min.txt"] + ], extend = true }, + { replace = "if", condition = "factor.jwst or factor.romancal", then = [ + [ + "bash", + "-c", + "pip freeze -q | grep 'stpipe @' > {env_tmp_dir}/requirements.txt" + ] + ], extend = true }, + { replace = "if", condition = "factor.jwst", then = [ + ["git", "clone", "https://github.com/spacetelescope/jwst.git"], + ["pip", "install", "-e", "jwst[test]"] + ], extend = true }, + { replace = "if", condition = "factor.romancal", then = [ + ["git", "clone", "https://github.com/spacetelescope/romancal.git"], + ["pip", "install", "-e", "romancal[test]"] + ], extend = true }, + { replace = "if", condition = "factor.jwst or factor.romancal", then = [ + ["pip", "install", "-r", "{env_tmp_dir}/requirements.txt"] + ], extend = true }, +] +commands = [ + [ + "pytest", + { replace = "if", condition = "factor.warnings", then = [ + "-W", + "error", + ], extend = true }, + { replace = "if", condition = "factor.nolegacypath", then = [ + "-p", + "no:legacypath", + ], extend = true }, + { replace = "if", condition = "factor.jwst", then = ["jwst"], extend = true }, + { replace = "if", condition = "factor.romancal", then = ["romancal"], extend = true }, + { replace = "if", condition = "factor.cov", then = [ + "--cov", + "--cov-report", + "xml:{toxinidir}/coverage.xml", + "--cov-report", + "term-missing", + ], extend = true }, + { replace = "if", condition = "factor.xdist", then = [ + "-n", + "auto", + ], extend = true }, + { replace = "posargs", extend = true }, + ], +]