├── .dockerignore ├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── documentation.yml │ └── feature_request.yml ├── actions │ └── setup-venv │ │ └── action.yml ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── main.yml │ ├── pr_checks.yml │ └── setup.yml ├── .gitignore ├── .readthedocs.yaml ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── RELEASE_PROCESS.md ├── docs ├── .gitignore ├── Makefile ├── make.bat └── source │ ├── CHANGELOG.md │ ├── CONTRIBUTING.md │ ├── _static │ ├── css │ │ └── custom.css │ └── favicon.ico │ ├── conf.py │ ├── index.md │ ├── installation.md │ └── overview.md ├── my_package ├── __init__.py ├── py.typed └── version.py ├── pyproject.toml ├── scripts ├── personalize.py ├── prepare_changelog.py ├── release.sh └── release_notes.py ├── setup-requirements.txt └── tests ├── __init__.py └── hello_test.py /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .github 3 | .mypy_cache 4 | .pytest_cache 5 | .venv 6 | __pycache__ 7 | *.egg-info 8 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for considering contributing! Please read this document to learn the various ways you can contribute to this project and how to go about doing it. 4 | 5 | ## Bug reports and feature requests 6 | 7 | ### Did you find a bug? 8 | 9 | First, do [a quick search](https://github.com/allenai/python-package-template/issues) to see whether your issue has already been reported. 10 | If your issue has already been reported, please comment on the existing issue. 11 | 12 | Otherwise, open [a new GitHub issue](https://github.com/allenai/python-package-template/issues). Be sure to include a clear title 13 | and description. The description should include as much relevant information as possible. The description should 14 | explain how to reproduce the erroneous behavior as well as the behavior you expect to see. Ideally you would include a 15 | code sample or an executable test case demonstrating the expected behavior. 16 | 17 | ### Do you have a suggestion for an enhancement or new feature? 18 | 19 | We use GitHub issues to track feature requests. Before you create a feature request: 20 | 21 | * Make sure you have a clear idea of the enhancement you would like. If you have a vague idea, consider discussing 22 | it first on a GitHub issue. 23 | * Check the documentation to make sure your feature does not already exist. 24 | * Do [a quick search](https://github.com/allenai/python-package-template/issues) to see whether your feature has already been suggested. 25 | 26 | When creating your request, please: 27 | 28 | * Provide a clear title and description. 29 | * Explain why the enhancement would be useful. It may be helpful to highlight the feature in other libraries. 30 | * Include code examples to demonstrate how the enhancement would be used. 31 | 32 | ## Making a pull request 33 | 34 | When you're ready to contribute code to address an open issue, please follow these guidelines to help us be able to review your pull request (PR) quickly. 35 | 36 | 1. **Initial setup** (only do this once) 37 | 38 |
Expand details 👇
39 | 40 | If you haven't already done so, please [fork](https://help.github.com/en/enterprise/2.13/user/articles/fork-a-repo) this repository on GitHub. 41 | 42 | Then clone your fork locally with 43 | 44 | git clone https://github.com/USERNAME/python-package-template.git 45 | 46 | or 47 | 48 | git clone git@github.com:USERNAME/python-package-template.git 49 | 50 | At this point the local clone of your fork only knows that it came from *your* repo, github.com/USERNAME/python-package-template.git, but doesn't know anything the *main* repo, [https://github.com/allenai/python-package-template.git](https://github.com/allenai/python-package-template). You can see this by running 51 | 52 | git remote -v 53 | 54 | which will output something like this: 55 | 56 | origin https://github.com/USERNAME/python-package-template.git (fetch) 57 | origin https://github.com/USERNAME/python-package-template.git (push) 58 | 59 | This means that your local clone can only track changes from your fork, but not from the main repo, and so you won't be able to keep your fork up-to-date with the main repo over time. Therefore you'll need to add another "remote" to your clone that points to [https://github.com/allenai/python-package-template.git](https://github.com/allenai/python-package-template). To do this, run the following: 60 | 61 | git remote add upstream https://github.com/allenai/python-package-template.git 62 | 63 | Now if you do `git remote -v` again, you'll see 64 | 65 | origin https://github.com/USERNAME/python-package-template.git (fetch) 66 | origin https://github.com/USERNAME/python-package-template.git (push) 67 | upstream https://github.com/allenai/python-package-template.git (fetch) 68 | upstream https://github.com/allenai/python-package-template.git (push) 69 | 70 | Finally, you'll need to create a Python 3 virtual environment suitable for working on this project. There a number of tools out there that making working with virtual environments easier. 71 | The most direct way is with the [`venv` module](https://docs.python.org/3.7/library/venv.html) in the standard library, but if you're new to Python or you don't already have a recent Python 3 version installed on your machine, 72 | we recommend [Miniconda](https://docs.conda.io/en/latest/miniconda.html). 73 | 74 | On Mac, for example, you can install Miniconda with [Homebrew](https://brew.sh/): 75 | 76 | brew install miniconda 77 | 78 | Then you can create and activate a new Python environment by running: 79 | 80 | conda create -n my-package python=3.9 81 | conda activate my-package 82 | 83 | Once your virtual environment is activated, you can install your local clone in "editable mode" with 84 | 85 | pip install -U pip setuptools wheel 86 | pip install -e .[dev] 87 | 88 | The "editable mode" comes from the `-e` argument to `pip`, and essential just creates a symbolic link from the site-packages directory of your virtual environment to the source code in your local clone. That way any changes you make will be immediately reflected in your virtual environment. 89 | 90 |
91 | 92 | 2. **Ensure your fork is up-to-date** 93 | 94 |
Expand details 👇
95 | 96 | Once you've added an "upstream" remote pointing to [https://github.com/allenai/python-package-template.git](https://github.com/allenai/python-package-template), keeping your fork up-to-date is easy: 97 | 98 | git checkout main # if not already on main 99 | git pull --rebase upstream main 100 | git push 101 | 102 |
103 | 104 | 3. **Create a new branch to work on your fix or enhancement** 105 | 106 |
Expand details 👇
107 | 108 | Committing directly to the main branch of your fork is not recommended. It will be easier to keep your fork clean if you work on a separate branch for each contribution you intend to make. 109 | 110 | You can create a new branch with 111 | 112 | # replace BRANCH with whatever name you want to give it 113 | git checkout -b BRANCH 114 | git push -u origin BRANCH 115 | 116 |
117 | 118 | 4. **Test your changes** 119 | 120 |
Expand details 👇
121 | 122 | Our continuous integration (CI) testing runs [a number of checks](https://github.com/allenai/python-package-template/actions) for each pull request on [GitHub Actions](https://github.com/features/actions). You can run most of these tests locally, which is something you should do *before* opening a PR to help speed up the review process and make it easier for us. 123 | 124 | First, you should run [`isort`](https://github.com/PyCQA/isort) and [`black`](https://github.com/psf/black) to make sure you code is formatted consistently. 125 | Many IDEs support code formatters as plugins, so you may be able to setup isort and black to run automatically everytime you save. 126 | For example, [`black.vim`](https://github.com/psf/black/tree/master/plugin) will give you this functionality in Vim. But both `isort` and `black` are also easy to run directly from the command line. 127 | Just run this from the root of your clone: 128 | 129 | isort . 130 | black . 131 | 132 | Our CI also uses [`ruff`](https://github.com/astral-sh/ruff) to lint the code base and [`mypy`](http://mypy-lang.org/) for type-checking. You should run both of these next with 133 | 134 | ruff check . 135 | 136 | and 137 | 138 | mypy . 139 | 140 | We also strive to maintain high test coverage, so most contributions should include additions to [the unit tests](https://github.com/allenai/python-package-template/tree/main/tests). These tests are run with [`pytest`](https://docs.pytest.org/en/latest/), which you can use to locally run any test modules that you've added or changed. 141 | 142 | For example, if you've fixed a bug in `my_package/a/b.py`, you can run the tests specific to that module with 143 | 144 | pytest -v tests/a/b_test.py 145 | 146 | If your contribution involves additions to any public part of the API, we require that you write docstrings 147 | for each function, method, class, or module that you add. 148 | See the [Writing docstrings](#writing-docstrings) section below for details on the syntax. 149 | You should test to make sure the API documentation can build without errors by running 150 | 151 | make docs 152 | 153 | If the build fails, it's most likely due to small formatting issues. If the error message isn't clear, feel free to comment on this in your pull request. 154 | 155 | And finally, please update the [CHANGELOG](https://github.com/allenai/python-package-template/blob/main/CHANGELOG.md) with notes on your contribution in the "Unreleased" section at the top. 156 | 157 | After all of the above checks have passed, you can now open [a new GitHub pull request](https://github.com/allenai/python-package-template/pulls). 158 | Make sure you have a clear description of the problem and the solution, and include a link to relevant issues. 159 | 160 | We look forward to reviewing your PR! 161 | 162 |
163 | 164 | ### Writing docstrings 165 | 166 | We use [Sphinx](https://www.sphinx-doc.org/en/master/index.html) to build our API docs, which automatically parses all docstrings 167 | of public classes and methods using the [autodoc](https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html) extension. 168 | Please refer to autoc's documentation to learn about the docstring syntax. 169 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: 🐛 Bug Report 2 | description: Create a report to help us reproduce and fix the bug 3 | labels: 'bug' 4 | 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: > 9 | #### Before submitting a bug, please make sure the issue hasn't been already addressed by searching through [the existing and past issues](https://github.com/allenai/python-package-template/issues?q=is%3Aissue+sort%3Acreated-desc+). 10 | - type: textarea 11 | attributes: 12 | label: 🐛 Describe the bug 13 | description: | 14 | Please provide a clear and concise description of what the bug is. 15 | 16 | If relevant, add a minimal example so that we can reproduce the error by running the code. It is very important for the snippet to be as succinct (minimal) as possible, so please take time to trim down any irrelevant code to help us debug efficiently. We are going to copy-paste your code and we expect to get the same result as you did: avoid any external data, and include the relevant imports, etc. For example: 17 | 18 | ```python 19 | # All necessary imports at the beginning 20 | import my_package 21 | 22 | # A succinct reproducing example trimmed down to the essential parts: 23 | assert False is True, "Oh no!" 24 | ``` 25 | 26 | If the code is too long (hopefully, it isn't), feel free to put it in a public gist and link it in the issue: https://gist.github.com. 27 | 28 | Please also paste or describe the results you observe instead of the expected results. If you observe an error, please paste the error message including the **full** traceback of the exception. It may be relevant to wrap error messages in ```` ```triple quotes blocks``` ````. 29 | placeholder: | 30 | A clear and concise description of what the bug is. 31 | validations: 32 | required: true 33 | - type: textarea 34 | attributes: 35 | label: Versions 36 | description: | 37 | Please run the following and paste the output below. 38 | ```sh 39 | python --version && pip freeze 40 | ``` 41 | validations: 42 | required: true 43 | - type: markdown 44 | attributes: 45 | value: > 46 | Thanks for contributing 🎉! 47 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation.yml: -------------------------------------------------------------------------------- 1 | name: 📚 Documentation 2 | description: Report an issue related to https://my-package.readthedocs.io/latest 3 | labels: 'documentation' 4 | 5 | body: 6 | - type: textarea 7 | attributes: 8 | label: 📚 The doc issue 9 | description: > 10 | A clear and concise description of what content in https://my-package.readthedocs.io/latest is an issue. 11 | validations: 12 | required: true 13 | - type: textarea 14 | attributes: 15 | label: Suggest a potential alternative/fix 16 | description: > 17 | Tell us how we could improve the documentation in this regard. 18 | - type: markdown 19 | attributes: 20 | value: > 21 | Thanks for contributing 🎉! 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: 🚀 Feature request 2 | description: Submit a proposal/request for a new feature 3 | labels: 'feature request' 4 | 5 | body: 6 | - type: textarea 7 | attributes: 8 | label: 🚀 The feature, motivation and pitch 9 | description: > 10 | A clear and concise description of the feature proposal. Please outline the motivation for the proposal. Is your feature request related to a specific problem? e.g., *"I'm working on X and would like Y to be possible"*. If this is related to another GitHub issue, please link here too. 11 | validations: 12 | required: true 13 | - type: textarea 14 | attributes: 15 | label: Alternatives 16 | description: > 17 | A description of any alternative solutions or features you've considered, if any. 18 | - type: textarea 19 | attributes: 20 | label: Additional context 21 | description: > 22 | Add any other context or screenshots about the feature request. 23 | - type: markdown 24 | attributes: 25 | value: > 26 | Thanks for contributing 🎉! 27 | -------------------------------------------------------------------------------- /.github/actions/setup-venv/action.yml: -------------------------------------------------------------------------------- 1 | name: Python virtualenv 2 | description: Set up a Python virtual environment with caching 3 | inputs: 4 | python-version: 5 | description: The Python version to use 6 | required: true 7 | cache-prefix: 8 | description: Update this to invalidate the cache 9 | required: true 10 | default: v0 11 | runs: 12 | using: composite 13 | steps: 14 | - name: Setup Python 15 | uses: actions/setup-python@v4 16 | with: 17 | python-version: ${{ inputs.python-version }} 18 | 19 | - shell: bash 20 | run: | 21 | # Install prerequisites. 22 | pip install --upgrade pip setuptools wheel virtualenv 23 | 24 | - shell: bash 25 | run: | 26 | # Get the exact Python version to use in the cache key. 27 | echo "PYTHON_VERSION=$(python --version)" >> $GITHUB_ENV 28 | 29 | - uses: actions/cache@v2 30 | id: virtualenv-cache 31 | with: 32 | path: .venv 33 | key: ${{ inputs.cache-prefix }}-${{ runner.os }}-${{ env.PYTHON_VERSION }}-${{ hashFiles('pyproject.toml') }} 34 | 35 | - if: steps.virtualenv-cache.outputs.cache-hit != 'true' 36 | shell: bash 37 | run: | 38 | # Set up virtual environment without cache hit. 39 | test -d .venv || virtualenv -p $(which python) --copies --reset-app-data .venv 40 | . .venv/bin/activate 41 | pip install -e .[dev] 42 | 43 | - if: steps.virtualenv-cache.outputs.cache-hit == 'true' 44 | shell: bash 45 | run: | 46 | # Set up virtual environment from cache hit. 47 | . .venv/bin/activate 48 | pip install --no-deps -e .[dev] 49 | 50 | - shell: bash 51 | run: | 52 | # Show environment info. 53 | . .venv/bin/activate 54 | echo "✓ Installed $(python --version) virtual environment to $(which python)" 55 | echo "Packages:" 56 | pip freeze 57 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "pip" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | open-pull-requests-limit: 10 8 | - package-ecosystem: "github-actions" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Fixes # 5 | 6 | Changes proposed in this pull request: 7 | 8 | - 9 | 10 | ## Before submitting 11 | 12 | 13 | - [ ] I've read and followed all steps in the [Making a pull request](https://github.com/allenai/python-package-template/blob/main/.github/CONTRIBUTING.md#making-a-pull-request) 14 | section of the `CONTRIBUTING` docs. 15 | - [ ] I've updated or added any relevant docstrings following the syntax described in the 16 | [Writing docstrings](https://github.com/allenai/python-package-template/blob/main/.github/CONTRIBUTING.md#writing-docstrings) section of the `CONTRIBUTING` docs. 17 | - [ ] If this PR fixes a bug, I've added a test that will fail without my fix. 18 | - [ ] If this PR adds a new feature, I've added tests that sufficiently cover my new functionality. 19 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Main 2 | 3 | concurrency: 4 | group: ${{ github.workflow }}-${{ github.ref }} 5 | cancel-in-progress: true 6 | 7 | on: 8 | pull_request: 9 | branches: 10 | - main 11 | push: 12 | branches: 13 | - main 14 | tags: 15 | - "v*.*.*" 16 | 17 | env: 18 | # Change this to invalidate existing cache. 19 | CACHE_PREFIX: v0 20 | PYTHONPATH: ./ 21 | 22 | jobs: 23 | checks: 24 | name: Python ${{ matrix.python }} - ${{ matrix.task.name }} 25 | runs-on: [ubuntu-latest] 26 | timeout-minutes: 15 27 | strategy: 28 | fail-fast: false 29 | matrix: 30 | python: ["3.8", "3.10"] 31 | task: 32 | - name: Test 33 | run: | 34 | pytest -v --color=yes tests/ 35 | 36 | include: 37 | - python: "3.10" 38 | task: 39 | name: Lint 40 | run: ruff check . 41 | 42 | - python: "3.10" 43 | task: 44 | name: Type check 45 | run: mypy . 46 | 47 | - python: "3.10" 48 | task: 49 | name: Build 50 | run: | 51 | python -m build 52 | 53 | - python: "3.10" 54 | task: 55 | name: Style 56 | run: | 57 | isort --check . 58 | black --check . 59 | 60 | - python: "3.10" 61 | task: 62 | name: Docs 63 | run: cd docs && make html 64 | 65 | steps: 66 | - uses: actions/checkout@v4 67 | 68 | - name: Setup Python environment 69 | uses: ./.github/actions/setup-venv 70 | with: 71 | python-version: ${{ matrix.python }} 72 | cache-prefix: ${{ env.CACHE_PREFIX }} 73 | 74 | - name: Restore mypy cache 75 | if: matrix.task.name == 'Type check' 76 | uses: actions/cache@v4 77 | with: 78 | path: .mypy_cache 79 | key: mypy-${{ env.CACHE_PREFIX }}-${{ runner.os }}-${{ matrix.python }}-${{ hashFiles('*requirements.txt') }}-${{ github.ref }}-${{ github.sha }} 80 | restore-keys: | 81 | mypy-${{ env.CACHE_PREFIX }}-${{ runner.os }}-${{ matrix.python }}-${{ hashFiles('*requirements.txt') }}-${{ github.ref }} 82 | mypy-${{ env.CACHE_PREFIX }}-${{ runner.os }}-${{ matrix.python }}-${{ hashFiles('*requirements.txt') }} 83 | 84 | - name: ${{ matrix.task.name }} 85 | run: | 86 | . .venv/bin/activate 87 | ${{ matrix.task.run }} 88 | 89 | - name: Upload package distribution files 90 | if: matrix.task.name == 'Build' 91 | uses: actions/upload-artifact@v4 92 | with: 93 | name: package 94 | path: dist 95 | 96 | - name: Clean up 97 | if: always() 98 | run: | 99 | . .venv/bin/activate 100 | pip uninstall -y my-package 101 | 102 | release: 103 | name: Release 104 | runs-on: ubuntu-latest 105 | needs: [checks] 106 | if: startsWith(github.ref, 'refs/tags/') 107 | steps: 108 | - uses: actions/checkout@v4 109 | with: 110 | fetch-depth: 0 111 | 112 | - name: Setup Python 113 | uses: actions/setup-python@v4 114 | with: 115 | python-version: "3.10" 116 | 117 | - name: Install requirements 118 | run: | 119 | pip install --upgrade pip setuptools wheel build 120 | pip install -e .[dev] 121 | 122 | - name: Prepare environment 123 | run: | 124 | echo "RELEASE_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV 125 | echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV 126 | 127 | - name: Download package distribution files 128 | uses: actions/download-artifact@v3 129 | with: 130 | name: package 131 | path: dist 132 | 133 | - name: Generate release notes 134 | run: | 135 | python scripts/release_notes.py > ${{ github.workspace }}-RELEASE_NOTES.md 136 | 137 | - name: Publish package to PyPI 138 | run: | 139 | twine upload -u '${{ secrets.PYPI_USERNAME }}' -p '${{ secrets.PYPI_PASSWORD }}' dist/* 140 | 141 | - name: Publish GitHub release 142 | uses: softprops/action-gh-release@v1 143 | env: 144 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 145 | with: 146 | body_path: ${{ github.workspace }}-RELEASE_NOTES.md 147 | prerelease: ${{ contains(env.TAG, 'rc') }} 148 | files: | 149 | dist/* 150 | -------------------------------------------------------------------------------- /.github/workflows/pr_checks.yml: -------------------------------------------------------------------------------- 1 | name: PR Checks 2 | 3 | concurrency: 4 | group: ${{ github.workflow }}-${{ github.ref }} 5 | cancel-in-progress: true 6 | 7 | on: 8 | pull_request: 9 | branches: 10 | - main 11 | paths: 12 | - 'my_package/**' 13 | 14 | jobs: 15 | changelog: 16 | name: CHANGELOG 17 | runs-on: ubuntu-latest 18 | if: github.event_name == 'pull_request' 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | with: 23 | fetch-depth: 0 24 | 25 | - name: Check that CHANGELOG has been updated 26 | run: | 27 | # If this step fails, this means you haven't updated the CHANGELOG.md 28 | # file with notes on your contribution. 29 | git diff --name-only $(git merge-base origin/main HEAD) | grep '^CHANGELOG.md$' && echo "Thanks for helping keep our CHANGELOG up-to-date!" 30 | -------------------------------------------------------------------------------- /.github/workflows/setup.yml: -------------------------------------------------------------------------------- 1 | name: Setup 2 | 3 | concurrency: 4 | group: ${{ github.workflow }}-${{ github.ref }} 5 | cancel-in-progress: true 6 | 7 | on: 8 | pull_request: 9 | branches: 10 | - main 11 | push: 12 | branches: 13 | - main 14 | 15 | jobs: 16 | test_personalize: 17 | name: Personalize 18 | runs-on: [ubuntu-latest] 19 | timeout-minutes: 10 20 | steps: 21 | - uses: actions/checkout@v4 22 | 23 | - name: Setup Python 24 | uses: actions/setup-python@v4 25 | with: 26 | python-version: "3.8" 27 | cache: "pip" 28 | cache-dependency-path: "setup-requirements.txt" 29 | 30 | - name: Install prerequisites 31 | run: | 32 | pip install -r setup-requirements.txt 33 | 34 | - name: Run personalize script 35 | run: | 36 | python scripts/personalize.py --github-org epwalsh --github-repo new-repo --package-name new-package --yes 37 | 38 | - name: Verify changes 39 | shell: bash 40 | run: | 41 | set -eo pipefail 42 | # Check that 'new-package' replaced 'my-package' in some files. 43 | grep -q 'new-package' pyproject.toml .github/workflows/main.yml .github/CONTRIBUTING.md 44 | # Check that the new repo URL replaced the old one in some files. 45 | grep -q 'https://github.com/epwalsh/new-repo' pyproject.toml .github/CONTRIBUTING.md 46 | # Double check that there are no lingering mentions of old names. 47 | for pattern in 'my[-_]package' 'https://github.com/allenai/python-package-template'; do 48 | if find . -type f -not -path './.git/*' | xargs grep "$pattern"; then 49 | echo "Found ${pattern} where it shouldn't be!" 50 | exit 1 51 | fi 52 | done 53 | echo "All good!" 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # build artifacts 2 | 3 | .eggs/ 4 | .mypy_cache 5 | *.egg-info/ 6 | build/ 7 | dist/ 8 | pip-wheel-metadata/ 9 | 10 | 11 | # dev tools 12 | 13 | .envrc 14 | .python-version 15 | .idea 16 | .venv/ 17 | .vscode/ 18 | /*.iml 19 | pyrightconfig.json 20 | 21 | 22 | # jupyter notebooks 23 | 24 | .ipynb_checkpoints 25 | 26 | 27 | # miscellaneous 28 | 29 | .cache/ 30 | doc/_build/ 31 | *.swp 32 | .DS_Store 33 | 34 | 35 | # python 36 | 37 | *.pyc 38 | *.pyo 39 | __pycache__ 40 | 41 | 42 | # testing and continuous integration 43 | 44 | .coverage 45 | .pytest_cache/ 46 | .benchmarks 47 | 48 | # documentation build artifacts 49 | 50 | docs/build 51 | site/ 52 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | sphinx: 4 | configuration: docs/source/conf.py 5 | fail_on_warning: true 6 | 7 | python: 8 | version: "3.8" 9 | install: 10 | - method: pip 11 | path: . 12 | extra_requirements: 13 | - dev 14 | 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## Unreleased 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | https://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | https://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY : docs 2 | docs : 3 | rm -rf docs/build/ 4 | sphinx-autobuild -b html --watch my_package/ docs/source/ docs/build/ 5 | 6 | .PHONY : run-checks 7 | run-checks : 8 | isort --check . 9 | black --check . 10 | ruff check . 11 | mypy . 12 | CUDA_VISIBLE_DEVICES='' pytest -v --color=yes --doctest-modules tests/ my_package/ 13 | 14 | .PHONY : build 15 | build : 16 | rm -rf *.egg-info/ 17 | python -m build 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # python-package-template 2 | 3 | This is a template repository for Python package projects. 4 | 5 | ## In this README :point_down: 6 | 7 | - [Features](#features) 8 | - [Usage](#usage) 9 | - [Initial setup](#initial-setup) 10 | - [Creating releases](#creating-releases) 11 | - [Projects using this template](#projects-using-this-template) 12 | - [FAQ](#faq) 13 | - [Contributing](#contributing) 14 | 15 | ## Features 16 | 17 | This template repository comes with all of the boilerplate needed for: 18 | 19 | ⚙️ Robust (and free) CI with [GitHub Actions](https://github.com/features/actions): 20 | - Unit tests ran with [PyTest](https://docs.pytest.org) against multiple Python versions and operating systems. 21 | - Type checking with [mypy](https://github.com/python/mypy). 22 | - Linting with [ruff](https://astral.sh/ruff). 23 | - Formatting with [isort](https://pycqa.github.io/isort/) and [black](https://black.readthedocs.io/en/stable/). 24 | 25 | 🤖 [Dependabot](https://github.blog/2020-06-01-keep-all-your-packages-up-to-date-with-dependabot/) configuration to keep your dependencies up-to-date. 26 | 27 | 📄 Great looking API documentation built using [Sphinx](https://www.sphinx-doc.org/en/master/) (run `make docs` to preview). 28 | 29 | 🚀 Automatic GitHub and PyPI releases. Just follow the steps in [`RELEASE_PROCESS.md`](./RELEASE_PROCESS.md) to trigger a new release. 30 | 31 | ## Usage 32 | 33 | ### Initial setup 34 | 35 | 1. [Create a new repository](https://github.com/allenai/python-package-template/generate) from this template with the desired name of your project. 36 | 37 | *Your project name (i.e. the name of the repository) and the name of the corresponding Python package don't necessarily need to match, but you might want to check on [PyPI](https://pypi.org/) first to see if the package name you want is already taken.* 38 | 39 | 2. Create a Python 3.8 or newer virtual environment. 40 | 41 | *If you're not sure how to create a suitable Python environment, the easiest way is using [Miniconda](https://docs.conda.io/en/latest/miniconda.html). On a Mac, for example, you can install Miniconda using [Homebrew](https://brew.sh/):* 42 | 43 | ``` 44 | brew install miniconda 45 | ``` 46 | 47 | *Then you can create and activate a new Python environment by running:* 48 | 49 | ``` 50 | conda create -n my-package python=3.9 51 | conda activate my-package 52 | ``` 53 | 54 | 3. Now that you have a suitable Python environment, you're ready to personalize this repository. Just run: 55 | 56 | ``` 57 | pip install -r setup-requirements.txt 58 | python scripts/personalize.py 59 | ``` 60 | 61 | And then follow the prompts. 62 | 63 | :pencil: *NOTE: This script will overwrite the README in your repository.* 64 | 65 | 4. Commit and push your changes, then make sure all GitHub Actions jobs pass. 66 | 67 | 5. (Optional) If you plan on publishing your package to PyPI, add repository secrets for `PYPI_USERNAME` and `PYPI_PASSWORD`. To add these, go to "Settings" > "Secrets" > "Actions", and then click "New repository secret". 68 | 69 | *If you don't have PyPI account yet, you can [create one for free](https://pypi.org/account/register/).* 70 | 71 | 6. (Optional) If you want to deploy your API docs to [readthedocs.org](https://readthedocs.org), go to the [readthedocs dashboard](https://readthedocs.org/dashboard/import/?) and import your new project. 72 | 73 | Then click on the "Admin" button, navigate to "Automation Rules" in the sidebar, click "Add Rule", and then enter the following fields: 74 | 75 | - **Description:** Publish new versions from tags 76 | - **Match:** Custom Match 77 | - **Custom match:** v[vV] 78 | - **Version:** Tag 79 | - **Action:** Activate version 80 | 81 | Then hit "Save". 82 | 83 | *After your first release, the docs will automatically be published to [your-project-name.readthedocs.io](https://your-project-name.readthedocs.io/).* 84 | 85 | ### Creating releases 86 | 87 | Creating new GitHub and PyPI releases is easy. The GitHub Actions workflow that comes with this repository will handle all of that for you. 88 | All you need to do is follow the instructions in [RELEASE_PROCESS.md](./RELEASE_PROCESS.md). 89 | 90 | ## Projects using this template 91 | 92 | Here is an incomplete list of some projects that started off with this template: 93 | 94 | - [ai2-tango](https://github.com/allenai/tango) 95 | - [cached-path](https://github.com/allenai/cached_path) 96 | - [beaker-py](https://github.com/allenai/beaker-py) 97 | - [gantry](https://github.com/allenai/beaker-gantry) 98 | - [ip-bot](https://github.com/abe-101/ip-bot) 99 | - [atty](https://github.com/mstuttgart/atty) 100 | - [generate-sequences](https://github.com/MagedSaeed/generate-sequences) 101 | 102 | ☝️ *Want your work featured here? Just open a pull request that adds the link.* 103 | 104 | ## FAQ 105 | 106 | #### Should I use this template even if I don't want to publish my package? 107 | 108 | Absolutely! If you don't want to publish your package, just delete the `docs/` directory and the `release` job in [`.github/workflows/main.yml`](https://github.com/allenai/python-package-template/blob/main/.github/workflows/main.yml). 109 | 110 | ## Contributing 111 | 112 | If you find a bug :bug:, please open a [bug report](https://github.com/allenai/python-package-template/issues/new?assignees=&labels=bug&template=bug_report.md&title=). 113 | If you have an idea for an improvement or new feature :rocket:, please open a [feature request](https://github.com/allenai/python-package-template/issues/new?assignees=&labels=Feature+request&template=feature_request.md&title=). 114 | -------------------------------------------------------------------------------- /RELEASE_PROCESS.md: -------------------------------------------------------------------------------- 1 | # GitHub Release Process 2 | 3 | ## Steps 4 | 5 | 1. Update the version in `my_package/version.py`. 6 | 7 | 3. Run the release script: 8 | 9 | ```bash 10 | ./scripts/release.sh 11 | ``` 12 | 13 | This will commit the changes to the CHANGELOG and `version.py` files and then create a new tag in git 14 | which will trigger a workflow on GitHub Actions that handles the rest. 15 | 16 | ## Fixing a failed release 17 | 18 | If for some reason the GitHub Actions release workflow failed with an error that needs to be fixed, you'll have to delete both the tag and corresponding release from GitHub. After you've pushed a fix, delete the tag from your local clone with 19 | 20 | ```bash 21 | git tag -l | xargs git tag -d && git fetch -t 22 | ``` 23 | 24 | Then repeat the steps above. 25 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= -W 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.https://www.sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/source/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ../../CHANGELOG.md -------------------------------------------------------------------------------- /docs/source/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ../../.github/CONTRIBUTING.md -------------------------------------------------------------------------------- /docs/source/_static/css/custom.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allenai/python-package-template/6cf0e7394d0cf863f70eef9e4441182383ab393c/docs/source/_static/css/custom.css -------------------------------------------------------------------------------- /docs/source/_static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allenai/python-package-template/6cf0e7394d0cf863f70eef9e4441182383ab393c/docs/source/_static/favicon.ico -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | import logging 8 | import os 9 | import sys 10 | from datetime import datetime 11 | 12 | # -- Path setup -------------------------------------------------------------- 13 | 14 | # If extensions (or modules to document with autodoc) are in another directory, 15 | # add these directories to sys.path here. If the directory is relative to the 16 | # documentation root, use os.path.abspath to make it absolute, like shown here. 17 | # 18 | 19 | sys.path.insert(0, os.path.abspath("../../")) 20 | 21 | from my_package import VERSION, VERSION_SHORT # noqa: E402 22 | 23 | # -- Project information ----------------------------------------------------- 24 | 25 | project = "my-package" 26 | copyright = f"{datetime.today().year}, Allen Institute for Artificial Intelligence" 27 | author = "Allen Institute for Artificial Intelligence" 28 | version = VERSION_SHORT 29 | release = VERSION 30 | 31 | 32 | # -- General configuration --------------------------------------------------- 33 | 34 | # Add any Sphinx extension module names here, as strings. They can be 35 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 36 | # ones. 37 | extensions = [ 38 | "sphinx.ext.autodoc", 39 | "sphinx.ext.napoleon", 40 | "myst_parser", 41 | "sphinx.ext.intersphinx", 42 | "sphinx.ext.viewcode", 43 | "sphinx.ext.doctest", 44 | "sphinx_copybutton", 45 | "sphinx_autodoc_typehints", 46 | ] 47 | 48 | # Tell myst-parser to assign header anchors for h1-h3. 49 | myst_heading_anchors = 3 50 | 51 | suppress_warnings = ["myst.header"] 52 | 53 | # Add any paths that contain templates here, relative to this directory. 54 | templates_path = ["_templates"] 55 | 56 | # List of patterns, relative to source directory, that match files and 57 | # directories to ignore when looking for source files. 58 | # This pattern also affects html_static_path and html_extra_path. 59 | exclude_patterns = ["_build"] 60 | 61 | source_suffix = [".rst", ".md"] 62 | 63 | intersphinx_mapping = { 64 | "python": ("https://docs.python.org/3", None), 65 | # Uncomment these if you use them in your codebase: 66 | # "torch": ("https://pytorch.org/docs/stable", None), 67 | # "datasets": ("https://huggingface.co/docs/datasets/master/en", None), 68 | # "transformers": ("https://huggingface.co/docs/transformers/master/en", None), 69 | } 70 | 71 | # By default, sort documented members by type within classes and modules. 72 | autodoc_member_order = "groupwise" 73 | 74 | # Include default values when documenting parameter types. 75 | typehints_defaults = "comma" 76 | 77 | 78 | # -- Options for HTML output ------------------------------------------------- 79 | 80 | # The theme to use for HTML and HTML Help pages. See the documentation for 81 | # a list of builtin themes. 82 | # 83 | html_theme = "furo" 84 | 85 | html_title = f"my-package v{VERSION}" 86 | 87 | # Add any paths that contain custom static files (such as style sheets) here, 88 | # relative to this directory. They are copied after the builtin static files, 89 | # so a file named "default.css" will overwrite the builtin "default.css". 90 | html_static_path = ["_static"] 91 | 92 | html_css_files = ["css/custom.css"] 93 | 94 | html_favicon = "_static/favicon.ico" 95 | 96 | html_theme_options = { 97 | "footer_icons": [ 98 | { 99 | "name": "GitHub", 100 | "url": "https://github.com/allenai/python-package-template", 101 | "html": """ 102 | 103 | 104 | 105 | """, # noqa: E501 106 | "class": "", 107 | }, 108 | ], 109 | } 110 | 111 | # -- Hack to get rid of stupid warnings from sphinx_autodoc_typehints -------- 112 | 113 | 114 | class ShutupSphinxAutodocTypehintsFilter(logging.Filter): 115 | def filter(self, record: logging.LogRecord) -> bool: 116 | if "Cannot resolve forward reference" in record.msg: 117 | return False 118 | return True 119 | 120 | 121 | logging.getLogger("sphinx.sphinx_autodoc_typehints").addFilter(ShutupSphinxAutodocTypehintsFilter()) 122 | -------------------------------------------------------------------------------- /docs/source/index.md: -------------------------------------------------------------------------------- 1 | # **my-package** 2 | 3 | ```{toctree} 4 | :maxdepth: 2 5 | :hidden: 6 | :caption: Getting started 7 | 8 | installation 9 | overview 10 | ``` 11 | 12 | ```{toctree} 13 | :hidden: 14 | :caption: Development 15 | 16 | CHANGELOG 17 | CONTRIBUTING 18 | License 19 | GitHub Repository 20 | ``` 21 | 22 | ## Indices and tables 23 | 24 | ```{eval-rst} 25 | * :ref:`genindex` 26 | * :ref:`modindex` 27 | ``` 28 | -------------------------------------------------------------------------------- /docs/source/installation.md: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | **my-package** supports Python >= 3.8. 5 | 6 | ## Installing with `pip` 7 | 8 | **my-package** is available [on PyPI](https://pypi.org/project/my-package/). Just run 9 | 10 | ```bash 11 | pip install my-package 12 | ``` 13 | 14 | ## Installing from source 15 | 16 | To install **my-package** from source, first clone [the repository](https://github.com/allenai/python-package-template): 17 | 18 | ```bash 19 | git clone https://github.com/allenai/python-package-template.git 20 | cd python-package-template 21 | ``` 22 | 23 | Then run 24 | 25 | ```bash 26 | pip install -e . 27 | ``` 28 | -------------------------------------------------------------------------------- /docs/source/overview.md: -------------------------------------------------------------------------------- 1 | Overview 2 | ======== 3 | 4 | -------------------------------------------------------------------------------- /my_package/__init__.py: -------------------------------------------------------------------------------- 1 | from .version import VERSION, VERSION_SHORT 2 | -------------------------------------------------------------------------------- /my_package/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allenai/python-package-template/6cf0e7394d0cf863f70eef9e4441182383ab393c/my_package/py.typed -------------------------------------------------------------------------------- /my_package/version.py: -------------------------------------------------------------------------------- 1 | _MAJOR = "0" 2 | _MINOR = "1" 3 | # On main and in a nightly release the patch should be one ahead of the last 4 | # released build. 5 | _PATCH = "0" 6 | # This is mainly for nightly builds which have the suffix ".dev$DATE". See 7 | # https://semver.org/#is-v123-a-semantic-version for the semantics. 8 | _SUFFIX = "" 9 | 10 | VERSION_SHORT = "{0}.{1}".format(_MAJOR, _MINOR) 11 | VERSION = "{0}.{1}.{2}{3}".format(_MAJOR, _MINOR, _PATCH, _SUFFIX) 12 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | # See https://setuptools.pypa.io/en/latest/userguide/quickstart.html for more project configuration options. 7 | name = "my-package" 8 | dynamic = ["version"] 9 | readme = "README.md" 10 | classifiers = [ 11 | "Intended Audience :: Science/Research", 12 | "Development Status :: 3 - Alpha", 13 | "License :: OSI Approved :: Apache Software License", 14 | "Programming Language :: Python :: 3", 15 | "Topic :: Scientific/Engineering :: Artificial Intelligence", 16 | ] 17 | authors = [ 18 | {name = "Allen Institute for Artificial Intelligence", email = "contact@allenai.org"} 19 | ] 20 | requires-python = ">=3.8" 21 | dependencies = [ 22 | # Add your own dependencies here 23 | ] 24 | license = {file = "LICENSE"} 25 | 26 | [project.urls] 27 | Homepage = "https://github.com/allenai/python-package-template" 28 | Repository = "https://github.com/allenai/python-package-template" 29 | Changelog = "https://github.com/allenai/python-package-template/blob/main/CHANGELOG.md" 30 | # Documentation = "https://my-package.readthedocs.io/" 31 | 32 | [project.optional-dependencies] 33 | dev = [ 34 | "ruff", 35 | "mypy>=1.0,<2.0", 36 | "black>=23.0,<25.0", 37 | "isort>=5.12,<5.14", 38 | "pytest", 39 | "pytest-sphinx", 40 | "pytest-cov", 41 | "twine>=1.11.0", 42 | "build", 43 | "setuptools", 44 | "wheel", 45 | "Sphinx>=6.0,<9.0", 46 | "furo==2024.8.6", 47 | "myst-parser>=1.0", 48 | "sphinx-copybutton", 49 | "sphinx-autobuild", 50 | "sphinx-autodoc-typehints==1.23.3", 51 | "packaging" 52 | ] 53 | 54 | [tool.setuptools.packages.find] 55 | exclude = [ 56 | "*.tests", 57 | "*.tests.*", 58 | "tests.*", 59 | "tests", 60 | "docs*", 61 | "scripts*" 62 | ] 63 | 64 | [tool.setuptools] 65 | include-package-data = true 66 | 67 | [tool.setuptools.package-data] 68 | my_package = ["py.typed"] 69 | 70 | [tool.setuptools.dynamic] 71 | version = {attr = "my_package.version.VERSION"} 72 | 73 | [tool.black] 74 | line-length = 100 75 | include = '\.pyi?$' 76 | exclude = ''' 77 | ( 78 | __pycache__ 79 | | \.git 80 | | \.mypy_cache 81 | | \.pytest_cache 82 | | \.vscode 83 | | \.venv 84 | | \bdist\b 85 | | \bdoc\b 86 | ) 87 | ''' 88 | 89 | [tool.isort] 90 | profile = "black" 91 | multi_line_output = 3 92 | 93 | # You can override these pyright settings by adding a personal pyrightconfig.json file. 94 | [tool.pyright] 95 | reportPrivateImportUsage = false 96 | 97 | [tool.ruff] 98 | line-length = 115 99 | target-version = "py39" 100 | 101 | [tool.ruff.per-file-ignores] 102 | "__init__.py" = ["F401"] 103 | 104 | [tool.mypy] 105 | ignore_missing_imports = true 106 | no_site_packages = true 107 | check_untyped_defs = true 108 | 109 | [[tool.mypy.overrides]] 110 | module = "tests.*" 111 | strict_optional = false 112 | 113 | [tool.pytest.ini_options] 114 | testpaths = "tests/" 115 | python_classes = [ 116 | "Test*", 117 | "*Test" 118 | ] 119 | log_format = "%(asctime)s - %(levelname)s - %(name)s - %(message)s" 120 | log_level = "DEBUG" 121 | -------------------------------------------------------------------------------- /scripts/personalize.py: -------------------------------------------------------------------------------- 1 | """ 2 | Run this script once after first creating your project from this template repo to personalize 3 | it for own project. 4 | 5 | This script is interactive and will prompt you for various inputs. 6 | """ 7 | 8 | import sys 9 | from pathlib import Path 10 | from typing import Generator, List, Tuple 11 | 12 | import click 13 | from click_help_colors import HelpColorsCommand 14 | from rich import print 15 | from rich.markdown import Markdown 16 | from rich.prompt import Confirm 17 | from rich.syntax import Syntax 18 | from rich.traceback import install 19 | 20 | install(show_locals=True, suppress=[click]) 21 | 22 | REPO_BASE = (Path(__file__).parent / "..").resolve() 23 | 24 | FILES_TO_REMOVE = { 25 | REPO_BASE / ".github" / "workflows" / "setup.yml", 26 | REPO_BASE / "setup-requirements.txt", 27 | REPO_BASE / "scripts" / "personalize.py", 28 | } 29 | 30 | PATHS_TO_IGNORE = { 31 | REPO_BASE / "README.md", 32 | REPO_BASE / ".git", 33 | REPO_BASE / "docs" / "source" / "_static" / "favicon.ico", 34 | } 35 | 36 | GITIGNORE_LIST = [ 37 | line.strip() 38 | for line in (REPO_BASE / ".gitignore").open(encoding="utf-8").readlines() 39 | if line.strip() and not line.startswith("#") 40 | ] 41 | 42 | REPO_NAME_TO_REPLACE = "python-package-template" 43 | BASE_URL_TO_REPLACE = "https://github.com/allenai/python-package-template" 44 | 45 | 46 | @click.command( 47 | cls=HelpColorsCommand, 48 | help_options_color="green", 49 | help_headers_color="yellow", 50 | context_settings={"max_content_width": 115}, 51 | ) 52 | @click.option( 53 | "--github-org", 54 | prompt="GitHub organization or user (e.g. 'allenai')", 55 | help="The name of your GitHub organization or user.", 56 | ) 57 | @click.option( 58 | "--github-repo", 59 | prompt="GitHub repository (e.g. 'python-package-template')", 60 | help="The name of your GitHub repository.", 61 | ) 62 | @click.option( 63 | "--package-name", 64 | prompt="Python package name (e.g. 'my-package')", 65 | help="The name of your Python package.", 66 | ) 67 | @click.option( 68 | "-y", 69 | "--yes", 70 | is_flag=True, 71 | help="Run the script without prompting for a confirmation.", 72 | default=False, 73 | ) 74 | @click.option( 75 | "--dry-run", 76 | is_flag=True, 77 | hidden=True, 78 | default=False, 79 | ) 80 | def main( 81 | github_org: str, github_repo: str, package_name: str, yes: bool = False, dry_run: bool = False 82 | ): 83 | repo_url = f"https://github.com/{github_org}/{github_repo}" 84 | package_actual_name = package_name.replace("_", "-") 85 | package_dir_name = package_name.replace("-", "_") 86 | 87 | # Confirm before continuing. 88 | print(f"Repository URL set to: [link={repo_url}]{repo_url}[/]") 89 | print(f"Package name set to: [cyan]{package_actual_name}[/]") 90 | if not yes: 91 | yes = Confirm.ask("Is this correct?") 92 | if not yes: 93 | raise click.ClickException("Aborted, please run script again") 94 | 95 | # Personalize files. 96 | replacements = [ 97 | (BASE_URL_TO_REPLACE, repo_url), 98 | (REPO_NAME_TO_REPLACE, github_repo), 99 | ("my-package", package_actual_name), 100 | ("my_package", package_dir_name), 101 | ] 102 | if dry_run: 103 | for old, new in replacements: 104 | print(f"Replacing '{old}' with '{new}'") 105 | for path in iterfiles(REPO_BASE): 106 | if path.resolve() not in FILES_TO_REMOVE: 107 | personalize_file(path, dry_run, replacements) 108 | 109 | # Rename 'my_package' directory to `package_dir_name`. 110 | if not dry_run: 111 | (REPO_BASE / "my_package").replace(REPO_BASE / package_dir_name) 112 | else: 113 | print(f"Renaming 'my_package' directory to '{package_dir_name}'") 114 | 115 | # Start with a fresh README. 116 | readme_contents = f"""# {package_actual_name}\n""" 117 | if not dry_run: 118 | with open(REPO_BASE / "README.md", mode="w+t", encoding="utf-8") as readme_file: 119 | readme_file.write(readme_contents) 120 | else: 121 | print("Replacing README.md contents with:\n", Markdown(readme_contents)) 122 | 123 | install_example = Syntax("pip install -e '.[dev]'", "bash") 124 | print( 125 | "[green]\N{check mark} Success![/] You can now install your package locally in development mode with:\n", 126 | install_example, 127 | ) 128 | 129 | # Lastly, remove that we don't need. 130 | for path in FILES_TO_REMOVE: 131 | assert path.is_file(), path 132 | if not dry_run: 133 | if path.name == "personalize.py" and sys.platform.startswith("win"): 134 | # We can't unlink/remove an open file on Windows. 135 | print("You can remove the 'scripts/personalize.py' file now") 136 | else: 137 | path.unlink() 138 | 139 | 140 | def iterfiles(dir: Path) -> Generator[Path, None, None]: 141 | assert dir.is_dir() 142 | for path in dir.iterdir(): 143 | if path in PATHS_TO_IGNORE: 144 | continue 145 | 146 | is_ignored_file = False 147 | for gitignore_entry in GITIGNORE_LIST: 148 | if path.relative_to(REPO_BASE).match(gitignore_entry): 149 | is_ignored_file = True 150 | break 151 | if is_ignored_file: 152 | continue 153 | 154 | if path.is_dir(): 155 | yield from iterfiles(path) 156 | else: 157 | yield path 158 | 159 | 160 | def personalize_file(path: Path, dry_run: bool, replacements: List[Tuple[str, str]]): 161 | with path.open(mode="r+t", encoding="utf-8") as file: 162 | filedata = file.read() 163 | 164 | should_update: bool = False 165 | for old, new in replacements: 166 | if filedata.count(old): 167 | should_update = True 168 | filedata = filedata.replace(old, new) 169 | 170 | if should_update: 171 | if not dry_run: 172 | with path.open(mode="w+t", encoding="utf-8") as file: 173 | file.write(filedata) 174 | else: 175 | print(f"Updating {path}") 176 | 177 | 178 | if __name__ == "__main__": 179 | main() 180 | -------------------------------------------------------------------------------- /scripts/prepare_changelog.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from pathlib import Path 3 | 4 | from my_package.version import VERSION 5 | 6 | 7 | def main(): 8 | changelog = Path("CHANGELOG.md") 9 | 10 | with changelog.open() as f: 11 | lines = f.readlines() 12 | 13 | insert_index: int = -1 14 | for i in range(len(lines)): 15 | line = lines[i] 16 | if line.startswith("## Unreleased"): 17 | insert_index = i + 1 18 | elif line.startswith(f"## [v{VERSION}]"): 19 | print("CHANGELOG already up-to-date") 20 | return 21 | elif line.startswith("## [v"): 22 | break 23 | 24 | if insert_index < 0: 25 | raise RuntimeError("Couldn't find 'Unreleased' section") 26 | 27 | lines.insert(insert_index, "\n") 28 | lines.insert( 29 | insert_index + 1, 30 | f"## [v{VERSION}](https://github.com/allenai/python-package-template/releases/tag/v{VERSION}) - " 31 | f"{datetime.now().strftime('%Y-%m-%d')}\n", 32 | ) 33 | 34 | with changelog.open("w") as f: 35 | f.writelines(lines) 36 | 37 | 38 | if __name__ == "__main__": 39 | main() 40 | -------------------------------------------------------------------------------- /scripts/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | TAG=$(python -c 'from my_package.version import VERSION; print("v" + VERSION)') 6 | 7 | read -p "Creating new release for $TAG. Do you want to continue? [Y/n] " prompt 8 | 9 | if [[ $prompt == "y" || $prompt == "Y" || $prompt == "yes" || $prompt == "Yes" ]]; then 10 | python scripts/prepare_changelog.py 11 | git add -A 12 | git commit -m "Bump version to $TAG for release" || true && git push 13 | echo "Creating new git tag $TAG" 14 | git tag "$TAG" -m "$TAG" 15 | git push --tags 16 | else 17 | echo "Cancelled" 18 | exit 1 19 | fi 20 | -------------------------------------------------------------------------------- /scripts/release_notes.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | """ 4 | Prepares markdown release notes for GitHub releases. 5 | """ 6 | 7 | import os 8 | from typing import List, Optional 9 | 10 | import packaging.version 11 | 12 | TAG = os.environ["TAG"] 13 | 14 | ADDED_HEADER = "### Added 🎉" 15 | CHANGED_HEADER = "### Changed ⚠️" 16 | FIXED_HEADER = "### Fixed ✅" 17 | REMOVED_HEADER = "### Removed 👋" 18 | 19 | 20 | def get_change_log_notes() -> str: 21 | in_current_section = False 22 | current_section_notes: List[str] = [] 23 | with open("CHANGELOG.md") as changelog: 24 | for line in changelog: 25 | if line.startswith("## "): 26 | if line.startswith("## Unreleased"): 27 | continue 28 | if line.startswith(f"## [{TAG}]"): 29 | in_current_section = True 30 | continue 31 | break 32 | if in_current_section: 33 | if line.startswith("### Added"): 34 | line = ADDED_HEADER + "\n" 35 | elif line.startswith("### Changed"): 36 | line = CHANGED_HEADER + "\n" 37 | elif line.startswith("### Fixed"): 38 | line = FIXED_HEADER + "\n" 39 | elif line.startswith("### Removed"): 40 | line = REMOVED_HEADER + "\n" 41 | current_section_notes.append(line) 42 | assert current_section_notes 43 | return "## What's new\n\n" + "".join(current_section_notes).strip() + "\n" 44 | 45 | 46 | def get_commit_history() -> str: 47 | new_version = packaging.version.parse(TAG) 48 | 49 | # Pull all tags. 50 | os.popen("git fetch --tags") 51 | 52 | # Get all tags sorted by version, latest first. 53 | all_tags = os.popen("git tag -l --sort=-version:refname 'v*'").read().split("\n") 54 | 55 | # Out of `all_tags`, find the latest previous version so that we can collect all 56 | # commits between that version and the new version we're about to publish. 57 | # Note that we ignore pre-releases unless the new version is also a pre-release. 58 | last_tag: Optional[str] = None 59 | for tag in all_tags: 60 | if not tag.strip(): # could be blank line 61 | continue 62 | version = packaging.version.parse(tag) 63 | if new_version.pre is None and version.pre is not None: 64 | continue 65 | if version < new_version: 66 | last_tag = tag 67 | break 68 | if last_tag is not None: 69 | commits = os.popen(f"git log {last_tag}..{TAG} --oneline --first-parent").read() 70 | else: 71 | commits = os.popen("git log --oneline --first-parent").read() 72 | return "## Commits\n\n" + commits 73 | 74 | 75 | def main(): 76 | print(get_change_log_notes()) 77 | print(get_commit_history()) 78 | 79 | 80 | if __name__ == "__main__": 81 | main() 82 | -------------------------------------------------------------------------------- /setup-requirements.txt: -------------------------------------------------------------------------------- 1 | click>=7.0,<9.0 2 | click-help-colors>=0.9.1,<0.10 3 | rich>=11.0,<14.0 4 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allenai/python-package-template/6cf0e7394d0cf863f70eef9e4441182383ab393c/tests/__init__.py -------------------------------------------------------------------------------- /tests/hello_test.py: -------------------------------------------------------------------------------- 1 | def test_hello(): 2 | print("Hello, World!") 3 | --------------------------------------------------------------------------------