diff --git a/.all-contributorsrc b/.all-contributorsrc new file mode 100644 index 0000000..6ce638a --- /dev/null +++ b/.all-contributorsrc @@ -0,0 +1,25 @@ +{ + "projectName": "call-report", + "projectOwner": "RNKuhns", + "repoType": "github", + "repoHost": "https://github.com", + "files": [ + "CONTRIBUTORS.md" + ], + "imageSize": 100, + "commit": false, + "contributorsPerLine": 7, + "contributorsSortAlphabetically": true, + "skipCi": true, + "contributors": [ + { + "login": "RNKuhns", + "name": "RNKuhns", + "avatar_url": "https://avatars.githubusercontent.com/RNKuhns", + "profile": "https://github.com/RNKuhns", + "contributions": [ + "code" + ] + } + ] +} diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 0000000..0aaf05a --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,23 @@ +coverage: + status: + project: + default: + target: 90% + threshold: 1% + informational: false + patch: + default: + target: 90% + threshold: 1% + informational: false + +comment: + require_changes: true + +github_checks: + annotations: true + +ignore: + - "docs/" + - "tests/" + - ".github/" diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..657a9bf --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,35 @@ +version: 2 +updates: + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "weekly" + time: "01:43" + commit-message: + include: scope + prefix: "[MNT] " + labels: + - "maintenance" + - "dependencies" + groups: + python-minor-patch: + update-types: + - "minor" + - "patch" + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + time: "01:43" + commit-message: + include: scope + prefix: "[MNT] " + labels: + - "maintenance" + - "dependencies" + groups: + actions-minor-patch: + update-types: + - "minor" + - "patch" diff --git a/.github/workflows/check-requirements.yml b/.github/workflows/check-requirements.yml new file mode 100644 index 0000000..7292997 --- /dev/null +++ b/.github/workflows/check-requirements.yml @@ -0,0 +1,51 @@ +name: check-requirements + +on: + push: + branches: [main] + paths: + - "pyproject.toml" + pull_request: + branches: [main] + paths: + - "pyproject.toml" + +permissions: + contents: read + +jobs: + check-requirements-in-sync: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Install pip-tools + run: pip install pip-tools + + - name: Regenerate requirements.txt + run: scripts/generate_requirements.sh + + - name: Check for drift in requirements.txt and report + run: | + if ! git diff --quiet requirements.txt; then + echo "::group::requirements.txt is out of sync — diff below" + git diff requirements.txt + echo "::endgroup::" + echo "::error::requirements.txt is out of sync with pyproject.toml. Fix locally with: ./scripts/generate_requirements.sh && git add requirements.txt && git commit" + # Mark that we need to upload the corrected file and fail + echo "DRIFT_DETECTED=true" >> "$GITHUB_ENV" + else + echo "requirements.txt is in sync." + fi + + - name: Fail if drift was detected + if: env.DRIFT_DETECTED == 'true' + run: exit 1 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index c71b21f..ea62826 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,41 +1,23 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: "CodeQL Advanced" +name: CodeQL on: push: - branches: [ "main" ] + branches: [main] pull_request: - branches: [ "main" ] + branches: [main] schedule: - - cron: '23 0 * * 6' + - cron: "0 6 * * 1" + +permissions: + contents: read jobs: analyze: - name: Analyze (${{ matrix.language }}) - # Runner size impacts CodeQL analysis time. To learn more, please see: - # - https://gh.io/recommended-hardware-resources-for-running-codeql - # - https://gh.io/supported-runners-and-hardware-resources - # - https://gh.io/using-larger-runners (GitHub.com only) - # Consider using larger runners or machines with greater resources for possible analysis time improvements. - runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + name: Analyze + runs-on: ubuntu-latest permissions: - # required for all workflows security-events: write - - # required to fetch internal or private CodeQL packs packages: read - - # only required for workflows in private repositories actions: read contents: read @@ -47,55 +29,20 @@ jobs: build-mode: none - language: python build-mode: none - # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift' - # Use `c-cpp` to analyze code written in C, C++ or both - # Use 'java-kotlin' to analyze code written in Java, Kotlin or both - # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both - # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, - # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. - # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how - # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - # Add any setup steps before running the `github/codeql-action/init` action. - # This includes steps like installing compilers or runtimes (`actions/setup-node` - # or others). This is typically only required for manual builds. - # - name: Setup runtime (example) - # uses: actions/setup-example@v1 - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v4 - with: - languages: ${{ matrix.language }} - build-mode: ${{ matrix.build-mode }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - - # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs - # queries: security-extended,security-and-quality - - # If the analyze step fails for one of the languages you are analyzing with - # "We were unable to automatically build your code", modify the matrix above - # to set the build mode to "manual" for that language. Then modify this step - # to build your code. - # â„šī¸ Command-line programs to run using the OS shell. - # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - - name: Run manual build steps - if: matrix.build-mode == 'manual' - shell: bash - run: | - echo 'If you are using a "manual" build mode for one or more of the' \ - 'languages you are analyzing, replace this with the commands to build' \ - 'your code, for example:' - echo ' make bootstrap' - echo ' make release' - exit 1 + steps: + - name: Checkout + uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: python + build-mode: none - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4 - with: - category: "/language:${{matrix.language}}" + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 + with: + category: "/language:python" diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 0000000..c83bd50 --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,88 @@ +name: pre-commit + +on: + push: + branches: [main] + pull_request: + branches: [main] + types: + - opened + - synchronize + - reopened + - labeled + - unlabeled + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Cache pre-commit environments + uses: actions/cache@v6 + with: + path: ~/.cache/pre-commit + key: pre-commit-${{ runner.os }}-py3.12-${{ hashFiles('.pre-commit-config.yaml') }} + restore-keys: | + pre-commit-${{ runner.os }}-py3.12- + + - name: Get changed files + if: ${{ github.event_name == 'pull_request' }} + # uses v47.0.5 + uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 + id: changed-files + + - name: List changed files + if: ${{ github.event_name == 'pull_request' }} + run: echo '${{ steps.changed-files.outputs.all_changed_files }}' + + # Check the full repository on pushes to main + - name: Full pre-commit + if: ${{ github.event_name == 'push' }} + uses: pre-commit/action@v3.0.1 + with: + extra_args: --all-files + + # Check the full repository on PRs that are explicitly labelled + - name: Full pre-commit on labelled PR + if: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'full-pre-commit') }} + uses: pre-commit/action@v3.0.1 + with: + extra_args: --all-files + + # Otherwise only check changed files on PRs + - name: Local pre-commit + if: ${{ github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'full-pre-commit') }} + uses: pre-commit/action@v3.0.1 + with: + extra_args: --files ${{ steps.changed-files.outputs.all_changed_files }} + + codespell-annotations: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Annotate locations with typos + uses: codespell-project/codespell-problem-matcher@v1 + + - name: Codespell + uses: codespell-project/actions-codespell@v2 diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml new file mode 100644 index 0000000..54ad387 --- /dev/null +++ b/.github/workflows/security.yml @@ -0,0 +1,37 @@ +name: security + +on: + push: + branches: [main] + pull_request: + branches: [main] + schedule: + - cron: "0 6 * * 1" + +permissions: + contents: read + +jobs: + pip-audit: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Install pip-tools + run: pip install pip-tools + + - name: Regenerate requirements.txt + run: scripts/generate_requirements.sh + + - name: Audit dependencies for known vulnerabilities + uses: pypa/gh-action-pip-audit@v1.1.0 + with: + inputs: requirements.txt diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..6d91b75 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,57 @@ +name: test + +on: + push: + branches: [main] + pull_request: + branches: [main] + paths: + - "call_report/**" + - ".github/workflows/**" + - "pyproject.toml" + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: true + + +jobs: + pytest: + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + + steps: + - name: Checkout + uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + + - name: Install package with dev dependencies + run: pip install -e ".[dev]" + + - name: Show dependencies + run: python -m pip list + + - name: Run tests + run: pytest --cov=call_report --cov-report=xml --cov-report=term-missing --cov-fail-under=90 + + - name: Upload coverage to Codecov + if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12' + uses: codecov/codecov-action@v7 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: ./coverage.xml + fail_ci_if_error: true diff --git a/.github/workflows/update-contributors.yml b/.github/workflows/update-contributors.yml new file mode 100644 index 0000000..7d8ef4b --- /dev/null +++ b/.github/workflows/update-contributors.yml @@ -0,0 +1,50 @@ +name: Update contributors + +on: + push: + branches: [main] + paths: + - .all-contributorsrc + +permissions: + contents: read + +jobs: + update-contributors: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: "18" + + - name: Setup all-contributors-cli + run: | + npm install -g all-contributors-cli + + - name: Regenerate CONTRIBUTORS.md + run: | + npx all-contributors generate + + - name: Create GitHub App token + id: app-token + uses: actions/create-github-app-token@v3 + with: + client-id: ${{ secrets.CLIENT_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + + - name: Open a PR with regenerated CONTRIBUTORS.md + uses: peter-evans/create-pull-request@v8 + with: + token: ${{ steps.app-token.outputs.token }} + commit-message: "automatic update of `CONTRIBUTORS.md`" + title: "[MNT] Automated CONTRIBUTORS.md update" + body: "Automated regeneration of CONTRIBUTORS.md from `.all-contributorsrc`." + branch: chore/update-contributors + delete-branch: true + labels: maintenance, no-changelog diff --git a/.github/workflows/weekly-maintenance.yml b/.github/workflows/weekly-maintenance.yml new file mode 100644 index 0000000..d3552cd --- /dev/null +++ b/.github/workflows/weekly-maintenance.yml @@ -0,0 +1,75 @@ +name: Weekly maintenance + +on: + schedule: + - cron: "0 6 * * 1" + workflow_dispatch: + +permissions: + contents: read + +jobs: + pre-commit-autoupdate: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Auto-update pre-commit hook revisions + uses: browniebroke/pre-commit-autoupdate-action@v1.0.1 + + - name: Create GitHub App token + id: app-token + uses: actions/create-github-app-token@v3 + with: + client-id: ${{ secrets.CLIENT_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + + - name: Open a PR with updated hooks + uses: peter-evans/create-pull-request@v8 + with: + token: ${{ steps.app-token.outputs.token }} + commit-message: "Update pre-commit hooks" + title: "[MNT] Update pre-commit hooks" + body: "Automated weekly update of `.pre-commit-config.yaml` hook revisions." + branch: chore/pre-commit-autoupdate + labels: maintenance, full-pre-commit, no-changelog + + scorecard: + runs-on: ubuntu-latest + permissions: + security-events: write + id-token: write + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Run OSSF Scorecard analysis + uses: ossf/scorecard-action@v2.4.3 + with: + results_file: results.sarif + results_format: sarif + publish_results: false + + - name: Upload artifact + uses: actions/upload-artifact@v7 + with: + name: SARIF file + path: results.sarif + retention-days: 14 + + - name: Upload SARIF to code scanning + uses: github/codeql-action/upload-sarif@v4 + with: + sarif_file: results.sarif diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..a295f25 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,17 @@ +version: 2 + +build: + os: ubuntu-24.04 + tools: + python: "3.12" + +python: + install: + - method: pip + path: . + extra_requirements: + - docs + +sphinx: + configuration: docs/conf.py + fail_on_warning: false diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md new file mode 100644 index 0000000..3f506be --- /dev/null +++ b/CONTRIBUTORS.md @@ -0,0 +1,23 @@ +# Contributors + +Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): + + + + + + + + + + +
RNKuhns
RNKuhns

đŸ’ģ
+ + + + + + +This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! + +This file is regenerated automatically from [.all-contributorsrc](.all-contributorsrc) by [.github/workflows/update-contributors.yml](.github/workflows/update-contributors.yml) — do not edit it by hand. diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..08835bd --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,19 @@ +# Minimal makefile for Sphinx documentation + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..90345f3 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,31 @@ +"""Sphinx configuration.""" + +from __future__ import annotations + +from call_report import __version__ + +project = "call-report" +copyright = "2026, RNKuhns" +author = "RNKuhns" +release = __version__ +version = ".".join(release.split(".")[:2]) + +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.intersphinx", + "sphinx.ext.viewcode", + "numpydoc", +] + +templates_path = ["_templates"] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + +html_theme = "pydata_sphinx_theme" + +numpydoc_show_class_members = False +autosummary_generate = True + +intersphinx_mapping = { + "python": ("https://docs.python.org/3", None), +} diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..120299f --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,9 @@ +call-report +=========== + +Tools for working with regulatory call report data filed by regulated U.S. +financial institutions. + +.. toctree:: + :maxdepth: 2 + :caption: Contents: diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..954237b --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/pyproject.toml b/pyproject.toml index d418db9..66d4899 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,11 @@ dev = [ "pre-commit>=3.7", "mypy>=1.10", ] +docs = [ + "sphinx>=7", + "pydata-sphinx-theme>=0.15", + "numpydoc>=1.7", +] [tool.hatch.build.targets.wheel] packages = ["src/call_report"] @@ -53,6 +58,7 @@ ignore = [] [tool.ruff.lint.per-file-ignores] "tests/*.py" = ["S101"] +"docs/conf.py" = ["A001"] [tool.ruff.lint.isort] known-first-party = ["call_report"] @@ -69,6 +75,20 @@ indent-style = "space" docstring-code-format = true docstring-code-line-length = "dynamic" +[tool.coverage.run] +source = ["call_report"] +branch = true + +[tool.coverage.report] +fail_under = 90 +show_missing = true +exclude_lines = [ + "pragma: no cover", + "if TYPE_CHECKING:", + "@abstractmethod", + "@abc.abstractmethod" +] + [tool.mypy] python_version = "3.10" files = ["src", "tests"] diff --git a/scripts/generate_requirements.sh b/scripts/generate_requirements.sh new file mode 100755 index 0000000..21c2883 --- /dev/null +++ b/scripts/generate_requirements.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$REPO_ROOT" + +if ! command -v pip-compile >/dev/null 2>&1; then + echo "Error: pip-compile not found. Install pip-tools first (pip install pip-tools)." >&2 + exit 1 +fi + +pip-compile pyproject.toml --all-extras --output-file=requirements.txt + +echo "Generated requirements.txt from pyproject.toml" diff --git a/scripts/tag_release.sh b/scripts/tag_release.sh old mode 100644 new mode 100755 diff --git a/tests/test_placeholder.py b/tests/test_placeholder.py index 1bc76ac..338a91b 100644 --- a/tests/test_placeholder.py +++ b/tests/test_placeholder.py @@ -5,4 +5,4 @@ def test_version() -> None: """Test that the package version is set correctly.""" - assert call_report.__version__ == "0.1.0" + assert call_report.__version__ == "0.1.1"