diff --git a/.github/CODE_OF_CONDUCT b/.github/CODE_OF_CONDUCT new file mode 100644 index 0000000..bdc7248 --- /dev/null +++ b/.github/CODE_OF_CONDUCT @@ -0,0 +1 @@ +Please see the [Team Culture and Conduct Standards](https://developer.lsst.io/team/code-of-conduct.html) for LSST Data Management. diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..ffdcf0a --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,16 @@ +Thanks for contributing to rubin_rag. + +## Support + +If you are a Rubin Observatory staff member, please reach out of us on Slack in the #dm-square channel or create a Jira ticket. + +If you are a community member, feel free to create a GitHub issue and we'll do our best to help you. + +## Pull requests + +Since rubin_rag is built for the Vera C. Rubin Observatory and Legacy Survey of Space and Time, community contributions can only be accepted if they align with the Rubin Observatory's mission. +For that reason, it's a good idea to propose changes with a new GitHub issue before investing time in making a pull request. + +* * * + +See also our [Code of Conduct](./CODE_OF_CONDUCT). diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..696b5d7 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..4614932 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,144 @@ +name: Python CI + +env: + # Default Python version used for all jobs other than test, which uses a + # matrix of supported versions. Quote the version to avoid interpretation as + # a floating point number. + PYTHON_VERSION: "3.12" + +"on": + merge_group: {} + pull_request: {} + push: + branches-ignore: + # These should always correspond to pull requests, so ignore them for + # the push trigger and let them be triggered by the pull_request + # trigger, avoiding running the workflow twice. This is a minor + # optimization so there's no need to ensure this is comprehensive. + - "dependabot/**" + - "gh-readonly-queue/**" + - "renovate/**" + - "tickets/**" + - "u/**" + release: + types: [published] + +jobs: + lint: + + runs-on: ubuntu-latest + timeout-minutes: 5 + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Run pre-commit + uses: pre-commit/action@v3.0.1 + + test: + + runs-on: ubuntu-latest + timeout-minutes: 10 + + strategy: + matrix: + python: + - "3.12" + + steps: + - uses: actions/checkout@v4 + + - name: Run tox + uses: lsst-sqre/run-tox@v1 + with: + python-version: ${{ matrix.python }} + tox-envs: "py,typing" + tox-plugins: tox-uv + + docs: + + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # full history for setuptools_scm + + - name: Install Graphviz + run: sudo apt-get install graphviz + + - name: Run tox + uses: lsst-sqre/run-tox@v1 + with: + python-version: ${{ env.PYTHON_VERSION }} + tox-envs: "docs" + # Add docs-linkcheck when the docs and PyPI package are published + # tox-envs: "docs,docs-linkcheck" + tox-plugins: tox-uv + + # Only attempt documentation uploads for tagged releases and pull + # requests from ticket branches in the same repository. This avoids + # version clutter in the docs and failures when a PR doesn't have access + # to secrets. + - name: Upload to LSST the Docs + uses: lsst-sqre/ltd-upload@v1 + with: + project: "rubin_rag" + dir: "docs/_build/html" + username: ${{ secrets.LTD_USERNAME }} + password: ${{ secrets.LTD_PASSWORD }} + if: > + github.event_name != 'merge_group' + && (github.event_name != 'pull_request' + || startsWith(github.head_ref, 'tickets/')) + + test-packaging: + + name: Test packaging + runs-on: ubuntu-latest + timeout-minutes: 5 + needs: [lint, test, docs] + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # full history for setuptools_scm + + - name: Build and publish + uses: lsst-sqre/build-and-publish-to-pypi@v2 + with: + python-version: ${{ env.PYTHON_VERSION }} + upload: false + + pypi: + + # This job requires set up: + # 1. Set up a trusted publisher for PyPI + # 2. Set up a "pypi" environment in the repository + # See https://github.com/lsst-sqre/build-and-publish-to-pypi + name: Upload release to PyPI + runs-on: ubuntu-latest + timeout-minutes: 10 + needs: [lint, test, docs, test-packaging] + environment: + name: pypi + url: https://pypi.org/p/rubin_rag + permissions: + id-token: write + if: github.event_name == 'release' && github.event.action == 'published' + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # full history for setuptools_scm + + - name: Build and publish + uses: lsst-sqre/build-and-publish-to-pypi@v2 + with: + python-version: ${{ env.PYTHON_VERSION }} diff --git a/.github/workflows/periodic-ci.yaml b/.github/workflows/periodic-ci.yaml new file mode 100644 index 0000000..ec2212b --- /dev/null +++ b/.github/workflows/periodic-ci.yaml @@ -0,0 +1,76 @@ +# This is a separate run of the Python test suite that doesn't cache the tox +# environment and runs from a schedule. The purpose is to test compatibility +# with the latest versions of dependencies. + +name: Periodic CI + +env: + # Default Python version used for all jobs other than test, which uses a + # matrix of supported versions. Quote the version to avoid interpretation as + # a floating point number. + PYTHON_VERSION: "3.12" + +"on": + schedule: + - cron: "0 12 * * 1" + workflow_dispatch: {} + +jobs: + test: + runs-on: ubuntu-latest + timeout-minutes: 10 + + strategy: + matrix: + python: + - "3.12" + + steps: + - uses: actions/checkout@v4 + + - name: Run tests in tox + uses: lsst-sqre/run-tox@v1 + with: + python-version: ${{ matrix.python }} + tox-envs: "lint,typing,py" + tox-plugins: tox-uv + use-cache: false + + + docs: + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # full history for setuptools_scm + + - name: Install Graphviz + run: sudo apt-get install graphviz + + - name: Build docs in tox + uses: lsst-sqre/run-tox@v1 + with: + python-version: ${{ env.PYTHON_VERSION }} + tox-envs: "docs,docs-linkcheck" + tox-plugins: tox-uv + use-cache: false + + + test-packaging: + runs-on: ubuntu-latest + timeout-minutes: 5 + needs: [test, docs] + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # full history for setuptools_scm + + - name: Build and publish + uses: lsst-sqre/build-and-publish-to-pypi@v2 + with: + python-version: ${{ env.PYTHON_VERSION }} + upload: false + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2afcff0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,130 @@ +# 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/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +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 + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# 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/ +docs/api/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.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__/ + +# 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/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..e4ca1f7 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,21 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-toml + - id: check-yaml + - id: trailing-whitespace + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.8.0 + hooks: + - id: ruff + args: [--fix, --exit-non-zero-on-fix] + - id: ruff-format + + - repo: https://github.com/adamchainz/blacken-docs + rev: 1.19.1 + hooks: + - id: blacken-docs + additional_dependencies: [black==24.10.0] + args: [-l, '79', -t, py312] diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..428ba81 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,7 @@ +# Change log + +rubin_rag is versioned with [semver](https://semver.org/). + +Find changes for the upcoming release in the project's [changelog.d directory](https://github.com/lsst-dm/rubin_rag/tree/main/changelog.d/). + + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..72836b5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Association of Universities for Research in Astronomy, Inc. (AURA) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f3aa6ae --- /dev/null +++ b/Makefile @@ -0,0 +1,45 @@ +.PHONY: help +help: + @echo "Make targets for rubin_rag:" + @echo "make clean - Remove generated files" + @echo "make init - Set up dev environment (install pre-commit hooks)" + @echo "make linkcheck - Check for broken links in documentation" + @echo "make update - Update pre-commit dependencies and run make init" + @echo "make update-deps - Update pre-commit dependencies" + +.PHONY: clean +clean: + rm -rf .tox + rm -rf docs/_build + rm -rf docs/api + +.PHONY: init +init: + pip install --upgrade uv + uv pip install --upgrade pre-commit tox tox-uv + uv pip install --upgrade -e ".[dev]" + pre-commit install + rm -rf .tox + +# This is defined as a Makefile target instead of only a tox command because +# if the command fails we want to cat output.txt, which contains the +# actually useful linkcheck output. tox unfortunately doesn't support this +# level of shell trickery after failed commands. +.PHONY: linkcheck +linkcheck: + sphinx-build --keep-going -n -W -T -b linkcheck docs \ + docs/_build/linkcheck \ + || (cat docs/_build/linkcheck/output.txt; exit 1) + +# update and update-deps aren't that meaningful for PyPI packages that do +# not have pinned Python package dependencies, but provide the same targets +# as the FastAPI Safir app template so that people can use the same command +# to update everything at the start of development (here, just pre-commit). +.PHONY: update +update: update-deps init + +.PHONY: update-deps +update-deps: + pip install --upgrade uv + uv pip install --upgrade pre-commit + pre-commit autoupdate diff --git a/README.md b/README.md new file mode 100644 index 0000000..3d48641 --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +# rubin_rag + +Rubin RAG-basd LLM Application +Learn more at https://rubin_rag.lsst.io + +Install from PyPI: + +```sh +pip install rubin_rag +``` + +rubin_rag is developed by Rubin Observatory at https://github.com/lsst-dm/rubin_rag. + +## Features + + + +## Developing rubin_rag + +The best way to start contributing to rubin_rag is by cloning this repository, creating a virtual environment, and running the `make init` command: + +```sh +git clone https://github.com/lsst-dm/rubin_rag.git +cd rubin_rag +make init +``` + +You can run tests and build documentation with [tox](https://tox.wiki/en/latest/): + +```sh +tox +``` + +To learn more about the individual environments: + +```sh +tox -av +``` + +[See the docs for more information.](https://rubin_rag.lsst.io/dev/development.html) diff --git a/changelog.d/_template.md.jinja b/changelog.d/_template.md.jinja new file mode 100644 index 0000000..6e644b8 --- /dev/null +++ b/changelog.d/_template.md.jinja @@ -0,0 +1,7 @@ + +{%- for cat in config.categories %} + +### {{ cat }} + +- +{%- endfor %} diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..3c1f3b5 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,2 @@ +_build +api diff --git a/docs/_rst_epilog.rst b/docs/_rst_epilog.rst new file mode 100644 index 0000000..afd04ce --- /dev/null +++ b/docs/_rst_epilog.rst @@ -0,0 +1,7 @@ +.. _Click: https://click.palletsprojects.com/ +.. _mypy: http://www.mypy-lang.org +.. _pre-commit: https://pre-commit.com +.. _pytest: https://docs.pytest.org/en/latest/ +.. _scriv: https://scriv.readthedocs.io/en/stable/ +.. _semver: https://semver.org/ +.. _tox: https://tox.wiki/en/latest/ diff --git a/docs/api.rst b/docs/api.rst new file mode 100644 index 0000000..e5cb9a2 --- /dev/null +++ b/docs/api.rst @@ -0,0 +1,8 @@ +:og:description: Comprehensive API documentation for rubinrag. + +#################### +Python API reference +#################### + +.. automodapi:: rubinrag + :include-all-objects: diff --git a/docs/changelog.md b/docs/changelog.md new file mode 120000 index 0000000..04c99a5 --- /dev/null +++ b/docs/changelog.md @@ -0,0 +1 @@ +../CHANGELOG.md \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..3fd6377 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1 @@ +from documenteer.conf.guide import * diff --git a/docs/dev/development.rst b/docs/dev/development.rst new file mode 100644 index 0000000..5c82b81 --- /dev/null +++ b/docs/dev/development.rst @@ -0,0 +1,165 @@ +################# +Development guide +################# + +This page provides procedures and guidelines for developing and contributing to rubin_rag. + +Scope of contributions +====================== + +rubin_rag is an open source package, meaning that you can contribute to rubin_rag itself, or fork rubin_rag for your own purposes. + +Since rubin_rag is intended for internal use by Rubin Observatory, community contributions can only be accepted if they align with Rubin Observatory's aims. +For that reason, it's a good idea to propose changes with a new `GitHub issue`_ before investing time in making a pull request. + +rubin_rag is developed by the Rubin Observatory SQuaRE team. + +.. _GitHub issue: https://github.com/lsst-dm/rubin_rag/issues/new + +.. _dev-environment: + +Setting up a local development environment +========================================== + +To develop rubin_rag, create a virtual environment with your method of choice (like virtualenvwrapper) and then clone or fork, and install: + +.. code-block:: sh + + git clone https://github.com/lsst-dm/rubin_rag.git + cd rubin_rag + make init + +This init step does three things: + +1. Installs rubin_rag in an editable mode with its "dev" extra that includes test and documentation dependencies. +2. Installs pre-commit and tox. +3. Installs the pre-commit hooks. + +You must have Docker installed and configured so that your user can start Docker containers in order to run the test suite. + +.. _pre-commit-hooks: + +Pre-commit hooks +================ + +The pre-commit hooks, which are automatically installed by running the :command:`make init` command on :ref:`set up `, ensure that files are valid and properly formatted. +Some pre-commit hooks automatically reformat code: + +``ruff`` + Automatically formats Python code and applies safe fixes to lint issues. + +``blacken-docs`` + Automatically formats Python code in reStructuredText documentation and docstrings. + +When these hooks fail, your Git commit will be aborted. +To proceed, stage the new modifications and proceed with your Git commit. + +.. _dev-run-tests: + +Running tests +============= + +To test the library, run tox_, which tests the library the same way that the CI workflow does: + +.. code-block:: sh + + tox run + +To see a listing of test environments, run: + +.. code-block:: sh + + tox list + +To run a specific test or list of tests, you can add test file names (and any other pytest_ options) after ``--`` when executing the ``py`` tox environment. +For example: + +.. code-block:: sh + + tox run -e py -- tests/database_test.py + +.. _dev-build-docs: + +Building documentation +====================== + +Documentation is built with Sphinx_: + +.. _Sphinx: https://www.sphinx-doc.org/en/master/ + +.. code-block:: sh + + tox run -e docs + +The built documentation is located in the :file:`docs/_build/html` directory. + +Updating pre-commit +=================== + +To update the versions of the pre-commit hooks, run: + +.. code-block:: sh + + pre-commit autoupdate + +You may wish to do this at the start of a development cycle so that you're using the latest versions of the linters. + +.. _dev-change-log: + +Updating the change log +======================= + +rubin_rag uses scriv_ to maintain its change log. + +When preparing a pull request, run :command:`scriv create`. +This will create a change log fragment in :file:`changelog.d`. +Edit that fragment, removing the sections that do not apply and adding entries fo this pull request. +You can pass the ``--edit`` flag to :command:`scriv create` to open the created fragment automatically in an editor. + +Change log entries use the following sections: + +.. rst-class:: compact + +- **Backward-incompatible changes** +- **New features** +- **Bug fixes** +- **Other changes** (for minor, patch-level changes that are not bug fixes, such as logging formatting changes or updates to the documentation) + +These entries will eventually be cut and pasted into the release description for the next release, so the Markdown for the change descriptions should be compatible with GitHub's Markdown conventions for the release description. +Specifically: + +- Each bullet point should be entirely on one line, even if it contains multiple sentences. + This is an exception to the normal documentation convention of a newline after each sentence. + Unfortunately, GitHub interprets those newlines as hard line breaks, so they would result in an ugly release description. +- Avoid using too much complex markup, such as nested bullet lists, since the formatting in the GitHub release description may not be what you expect and manually editing it is tedious. + +.. _style-guide: + +Style guide +=========== + +Code +---- + +- The code style follows :pep:`8`, though in practice lean on Black and isort to format the code for you. + +- Use :pep:`484` type annotations. + The ``tox run -e typing`` test environment, which runs mypy_, ensures that the project's types are consistent. + +- Write tests for Pytest_. + +Documentation +------------- + +- Follow the `LSST DM User Documentation Style Guide`_, which is primarily based on the `Google Developer Style Guide`_. + +- Document the Python API with numpydoc-formatted docstrings. + See the `LSST DM Docstring Style Guide`_. + +- Follow the `LSST DM ReStructuredTextStyle Guide`_. + In particular, ensure that prose is written **one-sentence-per-line** for better Git diffs. + +.. _`LSST DM User Documentation Style Guide`: https://developer.lsst.io/user-docs/index.html +.. _`Google Developer Style Guide`: https://developers.google.com/style/ +.. _`LSST DM Docstring Style Guide`: https://developer.lsst.io/python/style.html +.. _`LSST DM ReStructuredTextStyle Guide`: https://developer.lsst.io/restructuredtext/style.html diff --git a/docs/dev/index.rst b/docs/dev/index.rst new file mode 100644 index 0000000..0d567f1 --- /dev/null +++ b/docs/dev/index.rst @@ -0,0 +1,13 @@ +:og:description: Learn how to contribute to the rubin_rag open source project. + +############ +Contributing +############ + +Learn how to contribute to the rubin_rag open source project. + +.. toctree:: + :caption: Guides + + development + release diff --git a/docs/dev/release.rst b/docs/dev/release.rst new file mode 100644 index 0000000..84df9a3 --- /dev/null +++ b/docs/dev/release.rst @@ -0,0 +1,103 @@ +################# +Release procedure +################# + +This page gives an overview of how rubin_rag releases are made. +This information is only useful for maintainers. + +rubin_rag's releases are largely automated through GitHub Actions (see the `ci.yaml`_ workflow file for details). +When a semantic version tag is pushed to GitHub, `rubin_rag is released to PyPI`_ with that version. +Similarly, documentation is built and pushed for each version (see https://rubin_rag.lsst.io/v). + +.. _`rubin_rag is released to PyPI`: https://pypi.org/project/rubin_rag/ +.. _`ci.yaml`: https://github.com/lsst-dm/rubin_rag/blob/main/.github/workflows/ci.yaml + +.. _regular-release: + +Regular releases +================ + +Regular releases happen from the ``main`` branch after changes have been merged. +From the ``main`` branch you can release a new major version (``X.0.0``), a new minor version of the current major version (``X.Y.0``), or a new patch of the current major-minor version (``X.Y.Z``). +See :ref:`backport-release` to patch an earlier major-minor version. + +Release tags are semantic version identifiers following the :pep:`440` specification. + +1. Change log and documentation +------------------------------- + +Change log messages for each release are accumulated using scriv_. +See :ref:`dev-change-log` in the *Developer guide* for more details. + +When it comes time to make the release, there should be a collection of change log fragments in :file:`changelog.d`. +Those fragments will make up the change log for the new release. + +Review those fragments to determine the version number of the next release. +Safir follows semver_, so follow its rules to pick the next version: + +.. rst-class:: compact + +- If there are any backward-incompatible changes, incremeent the major version number and set the other numbers to 0. +- If there are any new features, increment the minor version number and set the patch version to 0. +- Otherwise, increment the patch version number. + +Then, run ``scriv collect --version `` specifying the version number you decided on. +This will delete the fragment files and collect them into :file:`CHANGELOG.md` under an entry for the new release. +Review that entry and edit it as needed (proofread, change the order to put more important things first, etc.). +scriv will put blank lines between entries from different files. +You may wish to remove those blank lines to ensure consistent formatting by various Markdown parsers. + +Finally, create a PR from those changes and merge it before continuing with the release process. + +2. GitHub release and tag +------------------------- + +Use `GitHub's Release feature `__ to create releases and their corresponding Git tags. + +1. Specify a tag from the appropriate branch (typically ``main``). + This tag's name is :pep:`440` and is usually formatted at ``X.Y.Z`` (without a ``v`` prefix). + +2. For the release name, repeat the version string. + +3. Fill in the release notes, copied from the release notes. + You can use GitHub's change log feature to additionally generate a list of PRs included in the release. + +The tag **must** follow the :pep:`440` specification since rubin_rag uses setuptools_scm_ to set version metadata based on Git tags. +In particular, **don't** prefix the tag with ``v``. + +.. _setuptools_scm: https://github.com/pypa/setuptools_scm + +The `ci.yaml`_ GitHub Actions workflow uploads the new release to PyPI and documentation to https://rubin_rag.lsst.io. + +.. _backport-release: + +Backport releases +================= + +The regular release procedure works from the main line of development on the ``master`` Git branch. +To create a release that patches an earlier major or minor version, you need to release from a **release branch.** + +Creating a release branch +------------------------- + +Release branches are named after the major and minor components of the version string: ``X.Y``. +If the release branch doesn't already exist, check out the latest patch for that major-minor version: + +.. code-block:: sh + + git checkout X.Y.Z + git checkout -b X.Y + git push -u + +Developing on a release branch +------------------------------ + +Once a release branch exists, it becomes the "main" branch for patches of that major-minor version. +Pull requests should be based on, and merged into, the release branch. + +If the development on the release branch is a backport of commits on the ``main`` branch, use ``git cherry-pick`` to copy those commits into a new pull request against the release branch. + +Releasing from a release branch +------------------------------- + +Releases from a release branch are equivalent to :ref:`regular releases `, except that the release branch takes the role of the ``main`` branch. diff --git a/docs/documenteer.toml b/docs/documenteer.toml new file mode 100644 index 0000000..645535f --- /dev/null +++ b/docs/documenteer.toml @@ -0,0 +1,16 @@ +[project] +title = "rubin_rag" +copyright = "2025 Association of Universities for Research in Astronomy, Inc. (AURA)" + +[project.python] +package = "rubin_rag" + +[sphinx] +rst_epilog_file = "_rst_epilog.rst" +disable_primary_sidebars = [ + "index", + "changelog", +] + +[sphinx.intersphinx.projects] +python = "https://docs.python.org/3/" diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..da20788 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,24 @@ +:og:description: Rubin RAG-basd LLM Application +:html_theme.sidebar_secondary.remove: + +######### +rubin_rag +######### + +Rubin RAG-basd LLM Application + +Install rubin_rag from PyPI: + +.. code-block:: bash + + pip install rubin_rag + +rubin_rag is developed on GitHub at https://github.com/lsst-dm/rubin_rag. + +.. toctree:: + :hidden: + + User guide + API + Change log + Contributing diff --git a/docs/user-guide/index.rst b/docs/user-guide/index.rst new file mode 100644 index 0000000..62b7ca0 --- /dev/null +++ b/docs/user-guide/index.rst @@ -0,0 +1,9 @@ +:og:description: Learn how to use rubin_rag. + +########## +User guide +########## + +.. .. toctree:: +.. :maxdepth: 2 +.. .. :titlesonly: diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..432316c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,133 @@ +[project] +# https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ +name = "rubin_rag" +description = "Rubin RAG-basd LLM Application" +license = {file = "LICENSE"} +readme= "README.md" +keywords = [ + "rubin", + "lsst", +] +# https://pypi.org/classifiers/ +classifiers = [ + "Development Status :: 4 - Beta", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.12", + "Intended Audience :: Developers", + "Natural Language :: English", + "Operating System :: POSIX", + "Typing :: Typed", +] +requires-python = ">=3.12" +dependencies = [] +dynamic = ["version"] + +[project.optional-dependencies] +dev = [ + # Testing + "coverage[toml]", + "pytest", + "pytest-asyncio", + "mypy", + # Documentation + "documenteer[guide]<2", + "scriv", +] + +[project.urls] +Homepage = "https://rubin_rag.lsst.io" +Source = "https://github.com/lsst-dm/rubin_rag" + +[build-system] +requires = [ + "setuptools>=61", + "wheel", + "setuptools_scm[toml]>=6.2" +] +build-backend = "setuptools.build_meta" + +[tool.setuptools_scm] + +[tool.setuptools.packages.find] +# https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html +where = ["src"] +include = ["rubinrag*"] + +[tool.black] +line-length = 79 +target-version = ["py312"] + +[tool.coverage.run] +parallel = true +branch = true +source = ["rubinrag"] + +[tool.coverage.paths] +source = ["src", ".tox/*/site-packages"] + +[tool.coverage.report] +show_missing = true +exclude_lines = [ + "pragma: no cover", + "def __repr__", + "if self.debug:", + "if settings.DEBUG", + "raise AssertionError", + "raise NotImplementedError", + "if 0:", + "if __name__ == .__main__.:", + "if TYPE_CHECKING:" +] + +[tool.mypy] +disallow_untyped_defs = true +disallow_incomplete_defs = true +ignore_missing_imports = true +local_partial_types = true +no_implicit_reexport = true +show_error_codes = true +strict_equality = true +warn_redundant_casts = true +warn_unreachable = true +warn_unused_ignores = true +# Uncomment and add the "pydantic.mypy" plugin if using Pydantic. Add the +# "sqlalchemy.ext.mypy.plugin" if using SQL Alchemy. +# plugins = [] + +[tool.pytest.ini_options] +asyncio_default_fixture_loop_scope = "function" +asyncio_mode = "strict" +# The python_files setting is not for test detection (pytest will pick up any +# test files named *_test.py without this setting) but to enable special +# assert processing in any non-test supporting files under tests. We +# conventionally put test support functions under tests.support and may +# sometimes use assert in test fixtures in conftest.py, and pytest only +# enables magical assert processing (showing a full diff on assert failures +# with complex data structures rather than only the assert message) in files +# listed in python_files. +python_files = ["tests/*.py", "tests/*/*.py"] + +# Use the generic Ruff configuration in ruff.toml and extend it with only +# project-specific settings. Add a [tool.ruff.lint.extend-per-file-ignores] +# section for project-specific ignore rules. +[tool.ruff] +extend = "ruff-shared.toml" + +[tool.ruff.lint.isort] +known-first-party = ["rubinrag", "tests"] +split-on-trailing-comma = false + +[tool.scriv] +categories = [ + "Backwards-incompatible changes", + "New features", + "Bug fixes", + "Other changes", +] +entry_title_template = "{{ version }} ({{ date.strftime('%Y-%m-%d') }})" +format = "md" +md_header_level = "2" +new_fragment_template = "file:changelog.d/_template.md.jinja" +skip_fragments = "_template.md.jinja" diff --git a/ruff-shared.toml b/ruff-shared.toml new file mode 100644 index 0000000..4e89f3f --- /dev/null +++ b/ruff-shared.toml @@ -0,0 +1,151 @@ +# Generic shared Ruff configuration file. It should be possible to use this +# file unmodified in different packages provided that one likes the style that +# it enforces. +# +# This file should be used from pyproject.toml as follows: +# +# [tool.ruff] +# extend = "ruff-shared.toml" +# +# It can then be extended with project-specific rules. A common additional +# setting in pyproject.toml is tool.ruff.lint.extend-per-file-ignores, to add +# additional project-specific ignore rules for specific paths. +# +# The rule used with Ruff configuration is to disable every non-deprecated +# lint rule that has legitimate exceptions that are not dodgy code, rather +# than cluttering code with noqa markers. This is therefore a reiatively +# relaxed configuration that errs on the side of disabling legitimate rules. +# +# Reference for settings: https://docs.astral.sh/ruff/settings/ +# Reference for rules: https://docs.astral.sh/ruff/rules/ +exclude = ["docs/**"] +line-length = 79 +target-version = "py312" + +[format] +docstring-code-format = true + +[lint] +ignore = [ + "A005", # we always use relative imports so this is not ambiguous + "ANN401", # sometimes Any is the right type + "ARG001", # unused function arguments are often legitimate + "ARG002", # unused method arguments are often legitimate + "ARG003", # unused class method arguments are often legitimate + "ARG005", # unused lambda arguments are often legitimate + "ASYNC109", # many async functions use asyncio.timeout internally + "BLE001", # we want to catch and report Exception in background tasks + "C414", # nested sorted is how you sort by multiple keys with reverse + "D102", # sometimes we use docstring inheritence + "D104", # don't see the point of documenting every package + "D105", # our style doesn't require docstrings for magic methods + "D106", # Pydantic uses a nested Config class that doesn't warrant docs + "D205", # our documentation style allows a folded first line + "EM101", # justification (duplicate string in traceback) is silly + "EM102", # justification (duplicate string in traceback) is silly + "FBT003", # positional booleans are normal for Pydantic field defaults + "FIX002", # point of a TODO comment is that we're not ready to fix it + "PD011", # attempts to enforce pandas conventions for all data types + "G004", # forbidding logging f-strings is appealing, but not our style + "RET505", # disagree that omitting else always makes code more readable + "PLR0911", # often many returns is clearer and simpler style + "PLR0913", # factory pattern uses constructors with many arguments + "PLR2004", # too aggressive about magic values + "PLW0603", # yes global is discouraged but if needed, it's needed + "S105", # good idea but too many false positives on non-passwords + "S106", # good idea but too many false positives on non-passwords + "S107", # good idea but too many false positives on non-passwords + "S603", # not going to manually mark every subprocess call as reviewed + "S607", # using PATH is not a security vulnerability + "SIM102", # sometimes the formatting of nested if statements is clearer + "SIM117", # sometimes nested with contexts are clearer + "TC001", # we decided to not maintain separate TYPE_CHECKING blocks + "TC002", # we decided to not maintain separate TYPE_CHECKING blocks + "TC003", # we decided to not maintain separate TYPE_CHECKING blocks + "TD003", # we don't require issues be created for TODOs + "TID252", # if we're going to use relative imports, use them always + "TRY003", # good general advice but lint is way too aggressive + "TRY301", # sometimes raising exceptions inside try is the best flow + "UP040", # PEP 695 type aliases not yet supported by mypy + + # The following settings should be disabled when using ruff format + # per https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules + "W191", + "E111", + "E114", + "E117", + "D206", + "D300", + "Q000", + "Q001", + "Q002", + "Q003", + "COM812", + "COM819", + "ISC001", + "ISC002", +] +select = ["ALL"] + +[lint.per-file-ignores] +"alembic/**" = [ + "INP001", # Alembic files are magical + "D103", # Alembic methods do not need docstrings + "D400", # Alembic migrations have their own docstring format +] +"noxfile.py" = [ + "T201", # print makes sense as output from nox rules +] +"src/*/handlers/**" = [ + "D103", # FastAPI handlers should not have docstrings +] +"*/src/*/handlers/**" = [ + "D103", # FastAPI handlers should not have docstrings +] +"tests/**" = [ + "C901", # tests are allowed to be complex, sometimes that's convenient + "D101", # tests don't need docstrings + "D103", # tests don't need docstrings + "PLR0915", # tests are allowed to be long, sometimes that's convenient + "PT012", # way too aggressive about limiting pytest.raises blocks + "S101", # tests should use assert + "S106", # tests are allowed to hard-code dummy passwords + "S301", # allow tests for whether code can be pickled + "SLF001", # tests are allowed to access private members +] +"*/tests/**" = [ + "C901", # tests are allowed to be complex, sometimes that's convenient + "D101", # tests don't need docstrings + "D103", # tests don't need docstrings + "PLR0915", # tests are allowed to be long, sometimes that's convenient + "PT012", # way too aggressive about limiting pytest.raises blocks + "S101", # tests should use assert + "S106", # tests are allowed to hard-code dummy passwords + "S301", # allow tests for whether code can be pickled + "SLF001", # tests are allowed to access private members +] +"tests/schema_test.py" = [ + "ASYNC221", # useful to run subprocess in async tests for Alembic +] +"*/tests/schema_test.py" = [ + "ASYNC221", # useful to run subprocess in async tests for Alembic +] + +# These are too useful as attributes or methods to allow the conflict with the +# built-in to rule out their use. +[lint.flake8-builtins] +builtins-ignorelist = [ + "all", + "any", + "help", + "id", + "list", + "type", +] + +[lint.flake8-pytest-style] +fixture-parentheses = false +mark-parentheses = false + +[lint.pydocstyle] +convention = "numpy" diff --git a/src/rubinrag/__init__.py b/src/rubinrag/__init__.py new file mode 100644 index 0000000..5526e9e --- /dev/null +++ b/src/rubinrag/__init__.py @@ -0,0 +1,16 @@ +"""Rubin RAG-basd LLM Application""" + +__all__ = ["__version__"] + +from importlib.metadata import PackageNotFoundError, version + +__version__: str +"""The version string of rubinrag +(PEP 440 / SemVer compatible). +""" + +try: + __version__ = version(__name__) +except PackageNotFoundError: + # package is not installed + __version__ = "0.0.0" diff --git a/src/rubinrag/py.typed b/src/rubinrag/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..d8c4dfb --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,4 @@ +"""Tests and test support modules.""" + +# This __init__.py file enables test modules to import support code +# inside tests/ diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..898ca87 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1 @@ +"""Pytest configuration and fixtures.""" diff --git a/tests/version_test.py b/tests/version_test.py new file mode 100644 index 0000000..8caf3b4 --- /dev/null +++ b/tests/version_test.py @@ -0,0 +1,12 @@ +"""Test the packaging.""" + +from __future__ import annotations + +from rubinrag import __version__ + + +def test_version() -> None: + """Ensure that the version is set.""" + assert isinstance(__version__, str) + # Indicates the package is not installed otherwise + assert __version__ != "0.0.0" diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..724d547 --- /dev/null +++ b/tox.ini @@ -0,0 +1,47 @@ +[tox] +envlist = py,coverage-report,typing,lint,docs,docs-linkcheck +isolated_build = True + +[testenv] +description = Run pytest against {envname}. +extras = + dev + +[testenv:py] +description = Run pytest +commands = + coverage run -m pytest {posargs} + +[testenv:coverage-report] +description = Compile coverage from each test run. +skip_install = true +deps = coverage[toml]>=5.0.2 +depends = + py +commands = + coverage combine + coverage report + +[testenv:typing] +description = Run mypy. +commands = + mypy src/rubinrag tests + +[testenv:lint] +description = Lint codebase by running pre-commit (Black, isort, Flake8). +skip_install = true +deps = + pre-commit +commands = pre-commit run --all-files + +[testenv:docs] +description = Build documentation (HTML) with Sphinx. +commands = + sphinx-build --keep-going -n -W -T -b html -d {envtmpdir}/doctrees docs docs/_build/html + +[testenv:docs-linkcheck] +description = Check links in the documentation. +allowlist_externals = + make +commands = + make linkcheck