├── .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 |
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 |
--------------------------------------------------------------------------------