diff --git a/template/.cz.toml b/template/.cz.toml new file mode 100644 index 0000000..ff8c8fb --- /dev/null +++ b/template/.cz.toml @@ -0,0 +1,6 @@ +[tool.commitizen] +bump_message = "build(version): :bookmark: update version from $current_version to $new_version" +update_changelog_on_bump = true +version_provider = "uv" +# Don't regenerate the changelog on every update +changelog_incremental = true diff --git a/template/.editorconfig b/template/.editorconfig new file mode 100644 index 0000000..6fd615b --- /dev/null +++ b/template/.editorconfig @@ -0,0 +1,23 @@ +# EditorConfig settings. Some editors will read these automatically; +# for those that don't, see here: http://editorconfig.org/ + +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +max_line_length = 88 + +# Have a bit shorter line length for text docs +[*.{txt,md,qmd}] +max_line_length = 72 +indent_size = 4 + +# Python always uses 4 spaces for tabs +[*.py] +indent_style = space +indent_size = 4 diff --git a/template/.github/pull_request_template.md b/template/.github/pull_request_template.md new file mode 100644 index 0000000..1d9a826 --- /dev/null +++ b/template/.github/pull_request_template.md @@ -0,0 +1,13 @@ +# Description + +This PR DESCRIBE CHANGES. + +Closes # + +This PR needs a quick/an in-depth review. + +## Checklist + +- [ ] Added or updated tests +- [ ] Updated documentation +- [ ] Ran `just run-all` diff --git a/template/.github/workflows/build-package.yml b/template/.github/workflows/build-package.yml new file mode 100644 index 0000000..1f8741e --- /dev/null +++ b/template/.github/workflows/build-package.yml @@ -0,0 +1,19 @@ +name: Build package + +on: + pull_request: + branches: + - main + push: + branches: + - main + +# Limit token permissions for security +permissions: read-all + +jobs: + build: + uses: seedcase-project/.github/.github/workflows/reusable-build-python.yml@main + # Permissions needed for pushing to the coverage branch. + permissions: + contents: write diff --git a/template/.github/workflows/build-website.yml b/template/.github/workflows/build-website.yml new file mode 100644 index 0000000..b24f3c4 --- /dev/null +++ b/template/.github/workflows/build-website.yml @@ -0,0 +1,17 @@ +name: Build website + +on: + push: + branches: + - main + +# Limit token permissions for security +permissions: read-all + +jobs: + build-website: + uses: seedcase-project/.github/.github/workflows/reusable-build-docs-with-python.yml@main + secrets: + netlify-token: ${{ secrets.NETLIFY_AUTH_TOKEN }} + # This is to allow using `gh` CLI + github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/template/.github/workflows/dependency-review.yml b/template/.github/workflows/dependency-review.yml new file mode 100644 index 0000000..8b29785 --- /dev/null +++ b/template/.github/workflows/dependency-review.yml @@ -0,0 +1,17 @@ +# Dependency Review Action +# +# This Action will scan dependency manifest files that change as part of a Pull Request, +# surfacing known-vulnerable versions of the packages declared or updated in the PR. +# Once installed, if the workflow run is marked as required, +# PRs introducing known-vulnerable packages will be blocked from merging. +# +# Source repository: https://github.com/actions/dependency-review-action +name: "Security: Dependency Review" +on: pull_request + +# Limit token permissions for security +permissions: read-all + +jobs: + dependency-review: + uses: seedcase-project/.github/.github/workflows/reusable-dependency-review.yml@main diff --git a/template/.github/workflows/release-package.yml b/template/.github/workflows/release-package.yml new file mode 100644 index 0000000..8aef6bc --- /dev/null +++ b/template/.github/workflows/release-package.yml @@ -0,0 +1,61 @@ +name: Release package + +on: + push: + branches: + - main + +# Limit token permissions for security +permissions: read-all + +jobs: + release: + # This job outputs env variables `previous_version` and `current_version`. + # Only give permissions for this job. + permissions: + contents: write + uses: seedcase-project/.github/.github/workflows/reusable-release-project.yml@main + with: + app-id: ${{ vars.UPDATE_VERSION_APP_ID }} + secrets: + update-version-gh-token: ${{ secrets.UPDATE_VERSION_TOKEN }} + + pypi-publish: + name: Publish to PyPI + runs-on: ubuntu-latest + # Only give permissions for this job. + permissions: + # IMPORTANT: mandatory for trusted publishing. + id-token: write + environment: + name: pypi + needs: + - release + if: ${{ needs.release.outputs.previous_version != needs.release.outputs.current_version }} + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + # Need to explicitly get the current version, otherwise it defaults to current commit + # (which is not the same as the release/version commit). + ref: ${{ needs.release.outputs.current_version }} + + # This workflow and the publish workflows are based on: + # - https://packaging.python.org/en/latest/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/ + # - https://www.andrlik.org/dispatches/til-use-uv-for-build-and-publish-github-actions/ + # - https://github.com/astral-sh/trusted-publishing-examples + - name: Set up uv + uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1 + + - name: Build distributions + # Builds dists from source and stores them in the dist/ directory. + run: uv build + + - name: Publish 📦 to PyPI + # Only publish if the option is explicitly set in the calling workflow. + run: uv publish --trusted-publishing always diff --git a/template/.github/workflows/scorecards.yml b/template/.github/workflows/scorecards.yml new file mode 100644 index 0000000..68827d2 --- /dev/null +++ b/template/.github/workflows/scorecards.yml @@ -0,0 +1,28 @@ +# This workflow uses actions that are not certified by GitHub. They are provided +# by a third-party and are governed by separate terms of service, privacy +# policy, and support documentation. +name: "Security: Scorecard" +on: + # For Branch-Protection check. Only the default branch is supported. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection + branch_protection_rule: + # To guarantee Maintained check is occasionally updated. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained + schedule: + - cron: '20 7 * * 2' + push: + branches: + - main + +# Declare default permissions as read only. +permissions: read-all + +jobs: + analysis: + name: Analysis + uses: seedcase-project/.github/.github/workflows/reusable-scorecards.yml@main + permissions: + # Needed to upload the results to code-scanning dashboard. + security-events: write + # Needed to publish results and get a badge (see publish_results below). + id-token: write diff --git a/template/.gitignore b/template/.gitignore new file mode 100644 index 0000000..6346a65 --- /dev/null +++ b/template/.gitignore @@ -0,0 +1,86 @@ +# Development files and folders +_ignore +bin/ +dev/ + +# Temporary files +*.tmp + +# Any IDE specific folders +.idea + +# Any .env files +.env +.env.* +**/.env.* +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Python specific content +venv +__pycache__/ +*.py[cod] + +# Python packaging and distribution +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# Python testing and code coverage +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +coverage.* +.cache +nosetests.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# MacOS +.DS_Store + +# Quarto +/.quarto/ +docs/.quarto/ +*.ipynb +*.quarto_ipynb +*.storage + +# Quartodoc +/docs/reference/ +objects.json + +# Website generation +_site +_book +public +site + + +# Misc files +*.log diff --git a/template/.pre-commit-config.yaml b/template/.pre-commit-config.yaml new file mode 100644 index 0000000..9171acc --- /dev/null +++ b/template/.pre-commit-config.yaml @@ -0,0 +1,30 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +ci: + autofix_commit_msg: "chore(pre-commit): :pencil2: automatic fixes" + autoupdate_commit_msg: "ci(pre-commit): :construction_worker: update pre-commit CI version" + +repos: + - repo: https://github.com/gitleaks/gitleaks + rev: v8.28.0 + hooks: + - id: gitleaks + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + + - repo: https://github.com/commitizen-tools/commitizen + rev: v4.8.3 + hooks: + - id: commitizen + + # Use the mirror since the main `typos` repo has tags for different + # sub-packages, which confuses pre-commit when it tries to find the latest + # version + - repo: https://github.com/adhtruong/mirrors-typos + rev: v1.34.0 + hooks: + - id: typos diff --git a/template/.python-version b/template/.python-version new file mode 100644 index 0000000..e4fba21 --- /dev/null +++ b/template/.python-version @@ -0,0 +1 @@ +3.12 diff --git a/template/.typos.toml b/template/.typos.toml new file mode 100644 index 0000000..bb4c721 --- /dev/null +++ b/template/.typos.toml @@ -0,0 +1,9 @@ +[files] +extend-exclude = [ + "*.json", + "*.css", + ".quarto/*", + "_site/*", + "_extensions/*", + ".coverage-report/*" +] diff --git a/template/.vscode/extensions.json b/template/.vscode/extensions.json new file mode 100644 index 0000000..571e08a --- /dev/null +++ b/template/.vscode/extensions.json @@ -0,0 +1,22 @@ +{ + "recommendations": [ + "eamodio.gitlens", + "github.vscode-github-actions", + "redhat.vscode-yaml", + "donjayamanne.githistory", + "felipecaputo.git-project-manager", + "GitHub.vscode-pull-request-github", + "ms-python.python", + "ms-python.vscode-pylance", + "matangover.mypy", + "njpwerner.autodocstring", + "quarto.quarto", + "ms-toolsai.jupyter", + "vivaxy.vscode-conventional-commits", + "charliermarsh.ruff", + "pshaddel.conventional-branch", + "tekumara.typos-vscode", + "EditorConfig.EditorConfig" + ], + "unwantedRecommendations": [] +} diff --git a/template/.vscode/google-notypes.mustache b/template/.vscode/google-notypes.mustache new file mode 100644 index 0000000..55477fa --- /dev/null +++ b/template/.vscode/google-notypes.mustache @@ -0,0 +1,41 @@ +{{! Copied from https://github.com/NilsJPWerner/autoDocstring/blob/master/src/docstring/templates/google-notypes.mustache with some edits }} +{{! Google Docstring Template without Types for Args, Returns or Yields }} +{{summaryPlaceholder}}. + +{{extendedSummaryPlaceholder}} +{{#parametersExist}} + +Args: +{{#args}} + {{var}}: {{descriptionPlaceholder}}. +{{/args}} +{{#kwargs}} + {{var}}: {{descriptionPlaceholder}}. Defaults to {{&default}}. +{{/kwargs}} +{{/parametersExist}} +{{#returnsExist}} + +Returns: +{{#returns}} + {{descriptionPlaceholder}}. +{{/returns}} +{{/returnsExist}} +{{#exceptionsExist}} + +Raises: +{{#exceptions}} + {{type}}: {{descriptionPlaceholder}}. +{{/exceptions}} +{{/exceptionsExist}} +{{#yieldsExist}} + +Yields: +{{#yields}} + {{descriptionPlaceholder}}. +{{/yields}} +{{/yieldsExist}} + +Examples: + ```{python} + {{descriptionPlaceholder}} + ``` diff --git a/template/.vscode/json.code-snippets b/template/.vscode/json.code-snippets new file mode 100644 index 0000000..d6ccc66 --- /dev/null +++ b/template/.vscode/json.code-snippets @@ -0,0 +1,66 @@ +{ + "Insert TODO formatting": { + "scope": "quarto,markdown", + "prefix": "TODO", + "body": [ + "" + ], + "description": "Insert TODO formatting" + }, + "Insert bash formatted text": { + "scope": "quarto,markdown", + "prefix": "bash", + "body": [ + "``` bash", + "${0:Write text here}", + "```" + ], + "description": "Insert bash formatted text" + }, + "Insert a hidden comment section": { + "scope": "quarto,markdown", + "prefix": "hidden", + "body": [ + "::: content-hidden", + "${0:Write comments here}", + ":::" + ], + "description": "Insert a hidden content section" + }, + "Insert a 2 col table": { + "scope": "quarto,markdown", + "prefix": "tbl2", + "body": [ + "|${1:title} |${0:title} |", + "| --- | --- |", + "| | |" + ], + "description": "Insert a 2 col table" + }, + "Insert a 3 col table": { + "scope": "quarto,markdown", + "prefix": "tbl3", + "body": [ + "|${1:title} |${2:title} |${0:title} |", + "| --- | --- | --- |", + "| | | |" + ], + "description": "Insert a 3 col table" + }, + "Insert paneltab": { + "scope": "quarto,markdown", + "prefix": "paneltab", + "body": [ + "::: panel-tabset", + "### ${0:Header}", + "", + "${1:Text body}", + "", + "### ${2:Header}", + "", + "${3:Text body}", + ":::" + ], + "description": "Insert paneltab (including two tabs here). If you want additional tabs, just include more headers." + } +} diff --git a/template/.vscode/launch.json b/template/.vscode/launch.json new file mode 100644 index 0000000..01e6c14 --- /dev/null +++ b/template/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "test-debugger", + "type": "debugpy", + "request": "launch", + "program": "${file}", + "purpose": [ + "debug-test" + ], + "console": "integratedTerminal", + "justMyCode": false + } + ] +} diff --git a/template/.vscode/python.code-snippets b/template/.vscode/python.code-snippets new file mode 100644 index 0000000..a64822a --- /dev/null +++ b/template/.vscode/python.code-snippets @@ -0,0 +1,18 @@ +{ + "Insert a Python test": { + "prefix": "test-gwt", + "body": [ + "def test_$1():", + " # Given", + " $2", + " ", + " # When", + " $3", + " ", + " # Then", + " $4", + "", + ], + "description": "Create Given-When-Then test" + } +} diff --git a/template/.vscode/settings.json b/template/.vscode/settings.json new file mode 100644 index 0000000..e5437b2 --- /dev/null +++ b/template/.vscode/settings.json @@ -0,0 +1,40 @@ +{ + "files.autoSave": "onFocusChange", + "editor.wordWrap": "off", + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports": "always" + }, + "git.autofetch": false, + "quarto.visualEditor.markdownWrap": "column", + "quarto.visualEditor.markdownWrapColumn": 72, + "autoDocstring.customTemplatePath": ".vscode/google-notypes.mustache", + "editor.tabCompletion": "on", + "editor.snippetSuggestions": "inline", + "conventional-branch.type": [ + "build", + "ci", + "docs", + "feat", + "fix", + "refactor", + "style", + "test", + "chore", + "perf", + "revert" + ], + "conventional-branch.format": "{Type}/{Branch}", + "[quarto][qmd]": { + "editor.formatOnSave": false + }, + "[python]": { + "editor.defaultFormatter": "charliermarsh.ruff" + }, + "python.defaultInterpreterPath": "${workspaceFolder}/.venv", + "python.languageServer": "Pylance", + "files.insertFinalNewline": true, + "python.testing.pytestEnabled": true, + "python.testing.pytestPath": "${workspaceFolder}/.venv/bin/pytest", + "mypy.runUsingActiveInterpreter": true, +} diff --git a/template/_publish.yml b/template/_publish.yml new file mode 100644 index 0000000..972233d --- /dev/null +++ b/template/_publish.yml @@ -0,0 +1,5 @@ +- source: project + netlify: + # TODO: Include Netlify details + - id: "" + url: "" diff --git a/template/_renderer.py b/template/_renderer.py new file mode 100644 index 0000000..954d06a --- /dev/null +++ b/template/_renderer.py @@ -0,0 +1,88 @@ +"""Custom quartodoc renderer that fixes the output of returns and raises sections.""" + +from __future__ import annotations + +from typing import Literal, Union + +from plum import dispatch +from quartodoc import MdRenderer, layout +from quartodoc._griffe_compat import docstrings as ds +from quartodoc.pandoc.blocks import DefinitionList +from quartodoc.renderers.md_renderer import ParamRow +from tabulate import tabulate + + +class Renderer(MdRenderer): # type: ignore[misc] + style = "seedcase" + + @dispatch + def render_header(self, el: layout.Doc) -> str: + """Render the header of a docstring, including any anchors.""" + _str_dispname = el.name + + _anchor = f"{{ #{el.obj.path} }}" + + # For lvl 1 headers, add a yml header with the ipynb-shell-interactivity setting + # to get all output from the cell + if self.crnt_header_level == 1: + return f"---\nipynb-shell-interactivity: all\ntitle: {_str_dispname}\n---" + return f"{'#' * self.crnt_header_level} {_str_dispname} {_anchor}" + + # returns ---- + + @dispatch + def render( + self, el: Union[ds.DocstringSectionReturns, ds.DocstringSectionRaises] + ) -> str: + rows = list(map(self.render, el.value)) + header = [ + "Type", + "Description", + ] # removed "Name" from header (only relevant when table-style is 'table') + return self._render_table(rows, header, "returns") + + def _render_table( + self, + rows: list[ParamRow], + headers: list[str], + style: Literal["parameters", "attributes", "returns"], + ) -> str: + if self.table_style == "description-list": + str_rows = str(DefinitionList([row.to_definition_list() for row in rows])) + # remove empty fields and their separators + return str_rows.replace( + "[:]{.parameter-annotation-sep} ", "" + ).replace("[]{.parameter-name} [:]{.parameter-annotation-sep} ", "") + + row_tuples = [row.to_tuple(style) for row in rows] + # remove empty fields + row_tuples_compact = [ + tuple(field for field in tup if field not in (None, "")) + for tup in row_tuples + ] + return tabulate(row_tuples_compact, headers=headers, tablefmt="github") + + # Summarize =============================================================== + + @dispatch + def summarize(self, el: layout.Section) -> str: + desc = f"\n\n{el.desc}" if el.desc is not None else "" + if el.title is not None: + header = f"## {el.title}{desc}" + elif el.subtitle is not None: + header = f"### {el.subtitle}{desc}" + else: + header = "" + + if el.contents: + thead = "| | |\n| --- | --- |" + + rendered = [] + for child in el.contents: + rendered.append(self.summarize(child)) + + str_func_table = "\n".join([thead, *rendered]) + # add colwidths + return f"{header}\n\n{str_func_table}\n\n" + ': {tbl-colwidths="[40,60]"}' + + return header diff --git a/template/docs/LICENSE.md b/template/docs/LICENSE.md new file mode 100644 index 0000000..8fc427d --- /dev/null +++ b/template/docs/LICENSE.md @@ -0,0 +1,369 @@ +# Creative Commons Attribution 4.0 International + +Creative Commons Corporation (“Creative Commons”) is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an “as-is” basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +**Using Creative Commons Public Licenses** + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright and +certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + +- **Considerations for licensors:** Our public licenses are intended + for use by those authorized to give the public permission to use + material in ways otherwise restricted by copyright and certain other + rights. Our licenses are irrevocable. Licensors should read and + understand the terms and conditions of the license they choose + before applying it. Licensors should also secure all rights + necessary before applying our licenses so that the public can reuse + the material as expected. Licensors should clearly mark any material + not subject to the license. This includes other CC-licensed + material, or material used under an exception or limitation to + copyright. [More considerations for + licensors](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensors). + +- **Considerations for the public:** By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If the + licensor’s permission is not necessary for any reason–for example, + because of any applicable exception or limitation to copyright–then + that use is not regulated by the license. Our licenses grant only + permissions under copyright and certain other rights that a licensor + has authority to grant. Use of the licensed material may still be + restricted for other reasons, including because others have + copyright or other rights in the material. A licensor may make + special requests, such as asking that all changes be marked or + described. Although not required by our licenses, you are encouraged + to respect those requests where reasonable. [More considerations for + the + public](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensees). + +## Creative Commons Attribution 4.0 International Public License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution 4.0 International Public License ("Public License"). To the +extent this Public License may be interpreted as a contract, You are +granted the Licensed Rights in consideration of Your acceptance of these +terms and conditions, and the Licensor grants You such rights in +consideration of benefits the Licensor receives from making the Licensed +Material available under these terms and conditions. + +### Section 1 – Definitions. + +a. **Adapted Material** means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material and + in which the Licensed Material is translated, altered, arranged, + transformed, or otherwise modified in a manner requiring permission + under the Copyright and Similar Rights held by the Licensor. For + purposes of this Public License, where the Licensed Material is a + musical work, performance, or sound recording, Adapted Material is + always produced where the Licensed Material is synched in timed + relation with a moving image. + +b. **Adapter's License** means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + +c. **Copyright and Similar Rights** means copyright and/or similar + rights closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or categorized. + For purposes of this Public License, the rights specified in Section + 2(b)(1)-(2) are not Copyright and Similar Rights. + +d. **Effective Technological Measures** means those measures that, in + the absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright Treaty + adopted on December 20, 1996, and/or similar international + agreements. + +e. **Exceptions and Limitations** means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + +f. **Licensed Material** means the artistic or literary work, database, + or other material to which the Licensor applied this Public License. + +g. **Licensed Rights** means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + +h. **Licensor** means the individual(s) or entity(ies) granting rights + under this Public License. + +i. **Share** means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such as + reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the public + may access the material from a place and at a time individually + chosen by them. + +j. **Sui Generis Database Rights** means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially equivalent + rights anywhere in the world. + +k. **You** means the individual or entity exercising the Licensed + Rights under this Public License. **Your** has a corresponding + meaning. + +### Section 2 – Scope. + +a. ***License grant.*** + + 1. Subject to the terms and conditions of this Public License, the + Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + A. reproduce and Share the Licensed Material, in whole or in + part; and + + B. produce, reproduce, and Share Adapted Material. + + 2. **Exceptions and Limitations.** For the avoidance of doubt, + where Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with its + terms and conditions. + + 3. **Term.** The term of this Public License is specified in + Section 6(a). + + 4. **Media and formats; technical modifications allowed.** The + Licensor authorizes You to exercise the Licensed Rights in all + media and formats whether now known or hereafter created, and to + make technical modifications necessary to do so. The Licensor + waives and/or agrees not to assert any right or authority to + forbid You from making technical modifications necessary to + exercise the Licensed Rights, including technical modifications + necessary to circumvent Effective Technological Measures. For + purposes of this Public License, simply making modifications + authorized by this Section 2(a)(4) never produces Adapted + Material. + + 5. **Downstream recipients.** + + A. **Offer from the Licensor – Licensed Material.** Every + recipient of the Licensed Material automatically receives an + offer from the Licensor to exercise the Licensed Rights under + the terms and conditions of this Public License. + + B. **No downstream restrictions.** You may not offer or impose + any additional or different terms or conditions on, or apply any + Effective Technological Measures to, the Licensed Material if + doing so restricts exercise of the Licensed Rights by any + recipient of the Licensed Material. + + 6. **No endorsement.** Nothing in this Public License constitutes + or may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, the + Licensor or others designated to receive attribution as provided + in Section 3(a)(1)(A)(i). + +b. ***Other rights.*** + + 1. Moral rights, such as the right of integrity, are not licensed + under this Public License, nor are publicity, privacy, and/or + other similar personality rights; however, to the extent + possible, the Licensor waives and/or agrees not to assert any + such rights held by the Licensor to the limited extent necessary + to allow You to exercise the Licensed Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this Public + License. + + 3. To the extent possible, the Licensor waives any right to collect + royalties from You for the exercise of the Licensed Rights, + whether directly or through a collecting society under any + voluntary or waivable statutory or compulsory licensing scheme. + In all other cases the Licensor expressly reserves any right to + collect such royalties. + +### Section 3 – License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + +a. ***Attribution.*** + + 1. If You Share the Licensed Material (including in modified form), + You must: + + A. retain the following if it is supplied by the Licensor with + the Licensed Material: + + i. identification of the creator(s) of the Licensed Material + and any others designated to receive attribution, in any + reasonable manner requested by the Licensor (including by + pseudonym if designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of warranties; + + v. a URI or hyperlink to the Licensed Material to the extent + reasonably practicable; + + B. indicate if You modified the Licensed Material and retain an + indication of any previous modifications; and + + C. indicate the Licensed Material is licensed under this Public + License, and include the text of, or the URI or hyperlink to, + this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + 4. If You Share Adapted Material You produce, the Adapter's License + You apply must not prevent recipients of the Adapted Material + from complying with this Public License. + +### Section 4 – Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that apply +to Your use of the Licensed Material: + +a. for the avoidance of doubt, Section 2(a)(1) grants You the right to + extract, reuse, reproduce, and Share all or a substantial portion of + the contents of the database; + +b. if You include all or a substantial portion of the database contents + in a database in which You have Sui Generis Database Rights, then + the database in which You have Sui Generis Database Rights (but not + its individual contents) is Adapted Material; and + +c. You must comply with the conditions in Section 3(a) if You Share all + or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + +### Section 5 – Disclaimer of Warranties and Limitation of Liability. + +a. **Unless otherwise separately undertaken by the Licensor, to the + extent possible, the Licensor offers the Licensed Material as-is and + as-available, and makes no representations or warranties of any kind + concerning the Licensed Material, whether express, implied, + statutory, or other. This includes, without limitation, warranties + of title, merchantability, fitness for a particular purpose, + non-infringement, absence of latent or other defects, accuracy, or + the presence or absence of errors, whether or not known or + discoverable. Where disclaimers of warranties are not allowed in + full or in part, this disclaimer may not apply to You.** + +b. **To the extent possible, in no event will the Licensor be liable to + You on any legal theory (including, without limitation, negligence) + or otherwise for any direct, special, indirect, incidental, + consequential, punitive, exemplary, or other losses, costs, + expenses, or damages arising out of this Public License or use of + the Licensed Material, even if the Licensor has been advised of the + possibility of such losses, costs, expenses, or damages. Where a + limitation of liability is not allowed in full or in part, this + limitation may not apply to You.** + +c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent possible, + most closely approximates an absolute disclaimer and waiver of all + liability. + +### Section 6 – Term and Termination. + +a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + +b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided it + is cured within 30 days of Your discovery of the violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations of + this Public License. + +c. For the avoidance of doubt, the Licensor may also offer the Licensed + Material under separate terms or conditions or stop distributing the + Licensed Material at any time; however, doing so will not terminate + this Public License. + +d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + +### Section 7 – Other Terms and Conditions. + +a. The Licensor shall not be bound by any additional or different terms + or conditions communicated by You unless expressly agreed. + +b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + +### Section 8 – Interpretation. + +a. For the avoidance of doubt, this Public License does not, and shall + not be interpreted to, reduce, limit, restrict, or impose conditions + on any use of the Licensed Material that could lawfully be made + without permission under this Public License. + +b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + +c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + +d. Nothing in this Public License constitutes or may be interpreted as + a limitation upon, or waiver of, any privileges and immunities that + apply to the Licensor or You, including from the legal processes of + any jurisdiction or authority. + +> Creative Commons is not a party to its public licenses. +> Notwithstanding, Creative Commons may elect to apply one of its public +> licenses to material it publishes and in those instances will be +> considered the “Licensor.” Except for the limited purpose of +> indicating that material is shared under a Creative Commons public +> license or as otherwise permitted by the Creative Commons policies +> published at +> [creativecommons.org/policies](http://creativecommons.org/policies), +> Creative Commons does not authorize the use of the trademark “Creative +> Commons” or any other trademark or logo of Creative Commons without +> its prior written consent including, without limitation, in connection +> with any unauthorized modifications to any of its public licenses or +> any other arrangements, understandings, or agreements concerning use +> of licensed material. For the avoidance of doubt, this paragraph does +> not form part of the public licenses. +> +> Creative Commons may be contacted at creativecommons.org diff --git a/template/docs/design/index.qmd b/template/docs/design/index.qmd new file mode 100644 index 0000000..b787241 --- /dev/null +++ b/template/docs/design/index.qmd @@ -0,0 +1,3 @@ +--- +title: "Design" +--- diff --git a/template/docs/guide/index.qmd b/template/docs/guide/index.qmd new file mode 100644 index 0000000..6f60c41 --- /dev/null +++ b/template/docs/guide/index.qmd @@ -0,0 +1,3 @@ +--- +title: "Guide" +--- diff --git a/template/justfile b/template/justfile new file mode 100644 index 0000000..279c94f --- /dev/null +++ b/template/justfile @@ -0,0 +1,94 @@ +@_default: + just --list --unsorted + +# Run all build-related recipes in the justfile +run-all: install-deps format-python check-python check-unused test-python check-security check-spelling check-commits build-website + +# List all TODO items in the repository +list-todos: + grep -R -n --exclude="*.code-snippets" "TODO" * + +# Install the pre-commit hooks +install-precommit: + # Install pre-commit hooks + uvx pre-commit install + # Run pre-commit hooks on all files + uvx pre-commit run --all-files + # Update versions of pre-commit hooks + uvx pre-commit autoupdate + +# Install Python package dependencies +install-deps: + uv sync --all-extras --dev + +# Run the Python tests +test-python: + uv run pytest + # Make the badge from the coverage report + uv run genbadge coverage \ + -i coverage.xml \ + -o htmlcov/coverage.svg + +# Check Python code for any errors that need manual attention +check-python: + # Check formatting + uv run ruff check . + # Check types + uv run mypy . + +# Reformat Python code to match coding style and general structure +format-python: + uv run ruff check --fix . + uv run ruff format . + +# Build the documentation website using Quarto +build-website: + # To let Quarto know where python is. + export QUARTO_PYTHON=.venv/bin/python3 + # Delete any previously built files from quartodoc. + # -f is to not give an error if the files don't exist yet. + rm -f docs/reference/*.qmd + uv run quartodoc build + uv run quarto render --execute + +# Run checks on commits with non-main branches +check-commits: + #!/bin/zsh + branch_name=$(git rev-parse --abbrev-ref HEAD) + number_of_commits=$(git rev-list --count HEAD ^main) + if [[ ${branch_name} != "main" && ${number_of_commits} -gt 0 ]] + then + uv run cz check --rev-range main..HEAD + else + echo "Can't either be on ${branch_name} or have more than ${number_of_commits}." + fi + +# Run basic security checks on the package +check-security: + uv run bandit -r src/ + +# Check for spelling errors in files +check-spelling: + uv run typos + +# Build the documentation as PDF using Quarto +build-pdf: + # To let Quarto know where python is. + export QUARTO_PYTHON=.venv/bin/python3 + uv run quarto install tinytex + # For generating images from Mermaid diagrams + uv run quarto install chromium + uv run quarto render --profile pdf --to pdf + find docs -name "mermaid-figure-*.png" -delete + +# Check for unused code in the package and its tests +check-unused: + # exit code=0: No unused code was found + # exit code=3: Unused code was found + # Three confidence values: + # - 100 %: function/method/class argument, unreachable code + # - 90 %: import + # - 60 %: attribute, class, function, method, property, variable + # There are some things should be ignored though, with the allowlist. + # Create an allowlist with `vulture --make-allowlist` + uv run vulture src/ tests/ **/vulture-allowlist.py diff --git a/template/mypy.ini b/template/mypy.ini new file mode 100644 index 0000000..f6c48ee --- /dev/null +++ b/template/mypy.ini @@ -0,0 +1,9 @@ +[mypy] +python_version = 3.12 +strict = True + +[mypy-quartodoc.*] +ignore_missing_imports = True + +[mypy-tests.*] +disallow_untyped_defs = False diff --git a/template/pytest.ini b/template/pytest.ini new file mode 100644 index 0000000..5b84f15 --- /dev/null +++ b/template/pytest.ini @@ -0,0 +1,8 @@ +[pytest] +# - A short traceback (tb) mode to make it easier to view +# - Use the `src/` package (importlib) +# - Use code coverage on the `src/` package +# - If tests fail, do not generate coverage report +# - Create the coverage report in XML (for badge), terminal, and HTML +# - Trigger failure if below 90% code coverage +addopts = --tb=short --import-mode=importlib --cov=src --no-cov-on-fail --cov-report=term --cov-report=xml --cov-report=html --cov-fail-under=90 diff --git a/template/ruff.toml b/template/ruff.toml new file mode 100644 index 0000000..9171953 --- /dev/null +++ b/template/ruff.toml @@ -0,0 +1,26 @@ +# Support Python 3.12+ +target-version = "py312" + +# In addition to the default formatters/linters, add these as well. +[lint] +extend-select = [ + # Add the `line-too-long` rule to the enforced rule set. + "E501", + # Add rule that all functions have docstrings. + "D", + # Add isort to list. + "I" +] +# Ignore missing docstring at the top of files +ignore = ["D100"] + +[lint.pydocstyle] +convention = "google" + +[format] +docstring-code-format = true + +[lint.per-file-ignores] +# ignore "Module imported but unused" error in all init files +"__init__.py" = ["F401"] +"**/tests/*" = ["D"] diff --git a/template/tests/__init__.py b/template/tests/__init__.py new file mode 100644 index 0000000..8e07441 --- /dev/null +++ b/template/tests/__init__.py @@ -0,0 +1 @@ +"""Module containing all tests.""" diff --git a/template/tools/vulture-allowlist.py b/template/tools/vulture-allowlist.py new file mode 100644 index 0000000..44a7814 --- /dev/null +++ b/template/tools/vulture-allowlist.py @@ -0,0 +1,2 @@ +# ruff: noqa +# mypy: ignore-errors