├── pyproject.toml ├── .github └── workflows │ ├── test.yml │ ├── publish.yml │ └── setup.yml └── README.md /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "placeholder" 3 | version = "0.1" 4 | 5 | [project.optional-dependencies] 6 | test = ["pytest"] 7 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push, pull_request] 4 | 5 | permissions: 6 | contents: read 7 | 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: Set up Python ${{ matrix.python-version }} 17 | uses: actions/setup-python@v5 18 | with: 19 | python-version: ${{ matrix.python-version }} 20 | cache: pip 21 | cache-dependency-path: pyproject.toml 22 | - name: Install dependencies 23 | run: | 24 | pip install '.[test]' 25 | - name: Run tests 26 | run: | 27 | if [ -d tests ]; then python -m pytest; else echo "Tests directory not found"; fi 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Template repository for creating new Python libraries 2 | 3 | This GitHub [template repository](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-repository-on-github/creating-a-repository-from-a-template) can be used to create a new repository with the skeleton of a Python library, based on the [python-lib](https://github.com/simonw/python-lib) cookiecutter. 4 | 5 | Start here: https://github.com/simonw/python-lib-template-repository/generate 6 | 7 | The name of your repository will be the name of the Python package that you publish to [PyPI](https://pypi.org/), so make sure that name is not taken already! 8 | 9 | Add a one-line description of your repository, then click "Create repository from template". 10 | 11 | ![Screenshot of the create repository interface](https://user-images.githubusercontent.com/9599/131230293-7ed5760e-b385-407e-bbf1-c6fc7540d3fe.png) 12 | 13 | Once created, your new repository will execute a GitHub Actions workflow that uses cookiecutter to rewrite the repository to the desired state. This make take 30 seconds or so. 14 | 15 | You can see an example of a repository generated using this template here: 16 | 17 | - https://github.com/simonw/python-lib-template-repository-demo 18 | 19 | ## GitHub Actions setup by this repository 20 | 21 | The `test.yml` GitHub Actions workflow will run your tests automatically any time you push a change to the repo. 22 | 23 | The `publish.yml` Action runs when you create a new GitHub release. It can build and upload your package to [PyPI](https://pypi.org/). 24 | 25 | For this to work you need to create an account on PyPI and configure your new GitHub repository as a "trusted publisher". 26 | 27 | See [Publishing your library as a package to PyPI](https://github.com/simonw/python-lib#publishing-your-library-as-a-package-to-pypi) for details. 28 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Python Package 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | permissions: 8 | contents: read 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] 16 | steps: 17 | - uses: actions/checkout@v4 18 | - name: Set up Python ${{ matrix.python-version }} 19 | uses: actions/setup-python@v5 20 | with: 21 | python-version: ${{ matrix.python-version }} 22 | cache: pip 23 | cache-dependency-path: pyproject.toml 24 | - name: Install dependencies 25 | run: | 26 | pip install '.[test]' 27 | - name: Run tests 28 | run: | 29 | python -m pytest 30 | build: 31 | runs-on: ubuntu-latest 32 | needs: [test] 33 | steps: 34 | - uses: actions/checkout@v4 35 | - name: Set up Python 36 | uses: actions/setup-python@v5 37 | with: 38 | python-version: "3.13" 39 | cache: pip 40 | cache-dependency-path: pyproject.toml 41 | - name: Install dependencies 42 | run: | 43 | pip install setuptools wheel build 44 | - name: Build 45 | run: | 46 | python -m build 47 | - name: Store the distribution packages 48 | uses: actions/upload-artifact@v4 49 | with: 50 | name: python-packages 51 | path: dist/ 52 | publish: 53 | name: Publish to PyPI 54 | runs-on: ubuntu-latest 55 | if: startsWith(github.ref, 'refs/tags/') 56 | needs: [build] 57 | environment: release 58 | permissions: 59 | id-token: write 60 | steps: 61 | - name: Download distribution packages 62 | uses: actions/download-artifact@v4 63 | with: 64 | name: python-packages 65 | path: dist/ 66 | - name: Publish to PyPI 67 | uses: pypa/gh-action-pypi-publish@release/v1 68 | 69 | -------------------------------------------------------------------------------- /.github/workflows/setup.yml: -------------------------------------------------------------------------------- 1 | name: Execute template to populate repository 2 | 3 | on: 4 | push: 5 | workflow_dispatch: 6 | 7 | permissions: 8 | contents: write 9 | 10 | jobs: 11 | setup-repo: 12 | if: ${{ !github.event.repository.is_template }} 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | with: 17 | fetch-depth: 0 18 | ref: ${{ github.head_ref }} 19 | 20 | - name: Install cookiecutter 21 | run: pip3 install cookiecutter 22 | 23 | - uses: actions/github-script@v7 24 | id: fetch-repo-and-user-details 25 | with: 26 | script: | 27 | const query = `query($owner:String!, $name:String!) { 28 | repository(owner:$owner, name:$name) { 29 | name 30 | description 31 | owner { 32 | login 33 | ... on User { 34 | name 35 | } 36 | ... on Organization { 37 | name 38 | } 39 | } 40 | } 41 | }`; 42 | const variables = { 43 | owner: context.repo.owner, 44 | name: context.repo.repo 45 | } 46 | const result = await github.graphql(query, variables) 47 | console.log(result) 48 | return result 49 | 50 | - name: Rebuild contents using cookiecutter 51 | env: 52 | INFO: ${{ steps.fetch-repo-and-user-details.outputs.result }} 53 | run: | 54 | export REPO_NAME=$(echo $INFO | jq -r '.repository.name') 55 | # Run cookiecutter 56 | pushd /tmp 57 | cookiecutter gh:simonw/python-lib --no-input \ 58 | lib_name=$REPO_NAME \ 59 | description="$(echo $INFO | jq -r .repository.description | sed 's/\"/\\\"/g')" \ 60 | github_username="$(echo $INFO | jq -r .repository.owner.login)" \ 61 | author_name="$(echo $INFO | jq -r .repository.owner.name)" 62 | popd 63 | # Move generated content to root directory of repo 64 | mv /tmp/$REPO_NAME/* . 65 | # And .gitignore too: 66 | mv /tmp/$REPO_NAME/.gitignore . 67 | # Delete the setup.yml workflow, it has served its purpose 68 | rm .github/workflows/setup.yml 69 | 70 | - name: Force push new repo contents 71 | uses: stefanzweifel/git-auto-commit-action@v4 72 | with: 73 | commit_message: "Initial library structure" 74 | push_options: --force 75 | --------------------------------------------------------------------------------