diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 0000000..aecae35 --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,9 @@ +comment: false +coverage: + status: + project: + default: + target: auto + patch: + default: + target: auto diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..b8b8480 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "monthly" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml new file mode 100644 index 0000000..31015f3 --- /dev/null +++ b/.github/workflows/docs.yaml @@ -0,0 +1,41 @@ +name: docs +on: + push: + branches: [main] + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: write + +jobs: + docs: + runs-on: ubuntu-latest + + steps: + - name: Check out repo + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: pip + cache-dependency-path: pyproject.toml + + - name: Install pip packages + run: | + pip install uv + uv pip install --system -r tests/requirements.txt + uv pip install --system .[docs] + + - name: Build docs + run: mkdocs build + id: build_docs + + - name: Rebuild and deploy docs + run: mkdocs gh-deploy --force + if: github.ref == 'refs/heads/main' && steps.build_docs.outcome == 'success' diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..5c97dee --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,32 @@ +name: release + +on: + release: + types: [published] + +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + id-token: write + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install dependencies + run: | + python -m pip install build + pip install -r tests/requirements.txt + pip install -e . + + - name: Build sdist + run: python -m build --sdist + + - name: Publish package + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 0000000..a38e367 --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,48 @@ +name: tests +on: + push: + branches: [main] + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + tests-base: + runs-on: ubuntu-latest + strategy: + fail-fast: true + matrix: + python-version: ["3.9", "3.12"] + + defaults: + run: + shell: bash -l {0} + + steps: + - name: Check out repo + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: pip + cache-dependency-path: pyproject.toml + + - name: Install pip packages + run: | + pip install uv + uv pip install --system -r tests/requirements.txt + uv pip install --system .[dev] + + - name: Run tests with pytest + run: pytest --cov=template --cov-report=xml + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + with: + fail_ci_if_error: true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2ade99 --- /dev/null +++ b/.gitignore @@ -0,0 +1,69 @@ +# Docs +.cache/ +docs/reference/* +*doctrees* +/site + +# Python ignores +__pycache__/ +*.py[cod] +*$py.class +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +pip-log.txt +pip-delete-this-directory.txt +.ipynb_checkpoints +profile_default/ +ipython_config.py +.spyderproject +.spyproject + +# Unit test ignores +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ +.mypy_cache/ +.dmypy.json +dmypy.json +.pyre/ +.pytype/ + +# OS ignores +.DS_Store + +# Editors ignores +.vscode* + +# Environments +.env +.venv +venv/ +env/ +ENV/ +env.bak/ +venv.bak/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..769e7e3 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,23 @@ +ci: + autoupdate_schedule: monthly + autofix_commit_msg: pre-commit auto-fixes + autoupdate_commit_msg: pre-commit autoupdate + +repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.3.7 + hooks: + - id: ruff + args: [--fix, --unsafe-fixes] + - id: ruff-format + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: end-of-file-fixer + - id: trailing-whitespace + + - repo: https://github.com/adamchainz/blacken-docs + rev: 1.16.0 + hooks: + - id: blacken-docs diff --git a/.sourcery.yaml b/.sourcery.yaml new file mode 100644 index 0000000..f8b248f --- /dev/null +++ b/.sourcery.yaml @@ -0,0 +1,5 @@ +github: + ignore_labels: + - sourcery-ignore +rule_settings: + python_version: "3.9" diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f494f7d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,21 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.0.2] + +### Added + +- Added `.pre-commit-config.yaml` + +### Changed + +- Use `ruff` for formatting + +## [0.0.1] + +### Added + +- The initial release! diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..56a5da8 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,131 @@ +# Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +- Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or advances of + any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, + without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..d0d58fa --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,28 @@ +BSD 3-Clause License + +Copyright (c) 2024 + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..ed492c2 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +graft src/template diff --git a/README.md b/README.md new file mode 100644 index 0000000..d03fef7 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# template + +A simple-to-use resource for creating open-source Python packages. diff --git a/docs/example_docs/about/changelog.md b/docs/example_docs/about/changelog.md new file mode 100644 index 0000000..786b75d --- /dev/null +++ b/docs/example_docs/about/changelog.md @@ -0,0 +1 @@ +--8<-- "CHANGELOG.md" diff --git a/docs/example_docs/about/conduct.md b/docs/example_docs/about/conduct.md new file mode 100644 index 0000000..01f2ea2 --- /dev/null +++ b/docs/example_docs/about/conduct.md @@ -0,0 +1 @@ +--8<-- "CODE_OF_CONDUCT.md" diff --git a/docs/example_docs/about/license.md b/docs/example_docs/about/license.md new file mode 100644 index 0000000..468ade8 --- /dev/null +++ b/docs/example_docs/about/license.md @@ -0,0 +1,5 @@ +# License + +```title="LICENSE.md" +--8<-- "LICENSE.md" +``` diff --git a/docs/example_docs/code/hints.md b/docs/example_docs/code/hints.md new file mode 100644 index 0000000..d2cc91a --- /dev/null +++ b/docs/example_docs/code/hints.md @@ -0,0 +1,23 @@ +# Type Hinting + +## Overview + +In the sample functions provided with the template repository, you will see something like: + +``` +def make_array(val: float, length: int = 3) -> NDArray: +``` + +If you aren't familiar with [type-hinting](https://docs.python.org/3/library/typing.html), that's what the `: float`, `: int`, and `-> NDArray` are indicating. They tell the user what the expected types are for each parameter and return. They are not enforced in any way; they are merely hints (as the name suggests). It is always advisable to use type hints in your code, so get in the habit of doing so! + +!!! Tip + + If you have to import a given function solely for type-hinting purposes, you should put it within an [`if TYPE_CHECKING` block](https://docs.python.org/3/library/typing.html#typing.TYPE_CHECKING) (as demonstrated in `/src/template/examples/sample.py`). It will then only be imported when using a type-checking utility, reducing the overall import time of your module. + +!!! Note + + You do not need to touch the `py.typed` file. It is a marker that Python uses to indicate that type-hinting should be used in any programs that depend on your code. + +## Type Checking + +As mentioned, the type hints are just that: hints. If you want to ensure that the types are strictly adhered to across your codebase, you can use [mypy](https://mypy-lang.org/) to do so. This is a slightly more advanced tool, however, so is not something you need to worry about right now. diff --git a/docs/example_docs/code/source.md b/docs/example_docs/code/source.md new file mode 100644 index 0000000..7e6f2a2 --- /dev/null +++ b/docs/example_docs/code/source.md @@ -0,0 +1,26 @@ +# Source Code + +## Adding Your Code + +All source code (i.e. your various modules, functions, classes, and so on) should be placed in the `/src/` directory. A sample file named `examples/sample.py` is included here as a representative example, which you should replace. + +All the code in the `src` directory can be imported now that you have installed your package. + +!!! Tip + + As an example, you can import and use the demonstration [template.examples.sample][] functions as follows: + + ```python + from MyPackageName.examples.sample import add, make_array + + print(add(1, 2)) # 3 + print(make_array(3, length=4)) # [3, 3, 3, 3] + ``` + +!!! Note + + For any subfolder within `src/` containing Python code, you must have an `__init__.py` file, which will tell Python that this is a module you can import. + +## Docstrings + +The code comments beneath each function are called docstrings. They should provide an overview of the purpose of the function, the various parameters, and the return values (if any). Here, we are using the [NumPy style](https://numpydoc.readthedocs.io/en/latest/format.html) docstrings, but you can pick a different style if you like later on. diff --git a/docs/example_docs/code/tests.md b/docs/example_docs/code/tests.md new file mode 100644 index 0000000..ff00c39 --- /dev/null +++ b/docs/example_docs/code/tests.md @@ -0,0 +1,15 @@ +# Testing + +## Overview + +Writing effective tests for your code is a crucial part of the programming process. It is the best way to ensure that changes you make to your codebase throughout the development process do not break the core functionality of your code. This may be your first time writing tests, but trust me that it is essential. + +## Pytest + +Put any unit tests in the `/tests` folder. A sample test (i.e. `/tests/sample/examples/test_sample.py`) is included as a representative example. + +!!! Note + + All your testing scripts should start with `test_` in the filename. + +When you installed the package with the `[dev]` extras, you installed everything you need to run your unit tests. To run the unit tests locally, run `pytest .` in the base directory. It will let you know if any tests fail and what the reason is for each failure. diff --git a/docs/example_docs/github/commits.md b/docs/example_docs/github/commits.md new file mode 100644 index 0000000..6a2400c --- /dev/null +++ b/docs/example_docs/github/commits.md @@ -0,0 +1,22 @@ +# Saving Your Work + +There are still a few more steps left, but at this point you will want to make sure to save your work! + +## Pushing Your Changes + +[Commit](https://github.com/git-guides/git-commit) any changes you've made and [push](https://github.com/git-guides/git-push) them to your repository + +If you are using a program like GitKraken, this will involve the following steps: + +1. Save your work. +2. Recommended: make a new branch for your work (e.g. `develop`) +3. Click "Stage all changes". +4. Add a helpful commit message. +5. Commit the changes. +6. Click "push". + +!!! Tip + + It is advisable to make changes in a new branch rather than in `main` so that you can ensure your unit tests pass before the code is merged into the codebase. + +Then go on GitHub to see your changes. Assuming you pushed your changes to a new branch, you'll likely see a message asking if you want to make a Pull Request to merge in your changes into the `main` branch. diff --git a/docs/example_docs/github/workflows.md b/docs/example_docs/github/workflows.md new file mode 100644 index 0000000..5db555f --- /dev/null +++ b/docs/example_docs/github/workflows.md @@ -0,0 +1,40 @@ +# GitHub Actions + +## Workflows + +The last major piece of the puzzle is GitHub Actions, which is an automated suite of workflows that run every time a commit or pull request is made. The GitHub workflows can be found in the `.github/workflows` folder. + +## Tests + +The `/.github/workflows/tests.yaml` file contains the workflow to have GitHub automatically run the full suite of tests on every commit and pull request. For the most basic case outlined here, you do not need to make any modifications (other than, perhaps, the desired Python versions you wish to test on). + +By default, the test suite is set up to install the following packages: + +```bash +pip install -r tests/requirements.txt +pip install .[dev] +``` + +As you can see above, it will install specific versions of the dependencies outlined in `/tests/requirements.txt`. Unlike `pyproject.toml`, you want to include specific versions here so that your test suite is reproducible. + +The `/.github/dependabot.yml` file is set up such that [Dependabot](https://docs.github.com/en/code-security/getting-started/dependabot-quickstart-guide) will automatically open pull requests to update any versions in your `/tests/requirements.txt` file as they come out so that your code will always be tested on the newest releases of the various dependencies. This will ensure that your code doesn't break as dependencies update, but if it does, you will know what needs fixing. + +## Documentation + +The `/.github/workflows/docs.yaml` file contains the workflow to have GitHub test the build process for the documentation and deploy it (if enabled). + +To have your documentation automatically deployed on a GitHub webpage: + +1. Go to the settings page of your repository. +2. Click the "Pages" section under "Code and automation." +3. Select "Deploy from a branch" under "Source" +4. Set the branch to be "gh-pages" with "/ (root)" as the folder. +5. Wait a minute and refresh the page. You'll see a message that your site is live with a URL to the documentation. + +![](../media/deploy_docs.png) + +Once this process is done, the documentation will be live and will update with each commit. + +## Release + +The `/.github/workflows/release.yaml` file contains the workflow to have GitHub upload your package to PyPI every time you mint a new release on GitHub. This is a slightly more advanced topic that you can read more about at a later time, but it's there for when you need it. diff --git a/docs/example_docs/installation/install.md b/docs/example_docs/installation/install.md new file mode 100644 index 0000000..816169d --- /dev/null +++ b/docs/example_docs/installation/install.md @@ -0,0 +1,17 @@ +# Pip Installing + +Now it's time to install your Python package! You will want to install your Python package in "editable" mode, which means you won't have to re-install your code every time you make updates to it. Additionally, you will want to install several optional dependencies (listed under the `[project.optional-dependencies]` header in `pyproject.toml`) to ensure that you can test and build the documentation for your code. + +With all this in mind, you will want to run the following in the command line from the base of the package directory: + +```bash +pip install -e .[dev,docs] +``` + +Here, the `-e` means editable mode, the `.` means the current directory, and the `[dev,docs]` means it will install the "dev" and "docs" optional dependency set listed in the `pyproject.toml` file. + +!!! Tip + + You should generally start from a clean Python environment, such as a new Conda environment if you are using Anaconda or one of its variants. + +To make sure you installed your package successfully, open a Python console and run `import `. It should return without any errors. If there are errors, it's likely because you forgot to replace a "template" placeholder with the name of your package. diff --git a/docs/example_docs/installation/pyproject.md b/docs/example_docs/installation/pyproject.md new file mode 100644 index 0000000..1450a53 --- /dev/null +++ b/docs/example_docs/installation/pyproject.md @@ -0,0 +1,34 @@ +# pyproject.toml + +The `pyproject.toml` file contains all of the necessary information on how Python will install your package. + +## Metadata + +There are several metadata-related fields that you will likely want to update. You should have already updated the `name` of the package in a prior step when you replaced "template" everywhere, but you will also want to change the following: + +- `description` +- `license` (if you changed the default `LICENSE.md` file) +- `authors` +- `keywords` + +Aside from the `name`, none of the above are strictly necessary and can be left as-is (or removed) if you are unsure. + +## Dependencies + +The most important fields to update are related to the dependencies: the Python packages that your own code relies on. This will ensure that they are automatically installed when installing your Python package. + +The required dependencies are listed under the `[project]` header in the `dependencies` field. By default, the template repository lists `["numpy"]`. Include any dependencies you want in this list, separated by commas. This should be all the packages you import in your code that are not standard Python libraries. + +!!! Tip + + Not sure what dependencies you need just yet? No problem. You can come back to this later. + +!!! Note + + If you know a specific minimum version is needed for your code, you should set that here as well (e.g. `["numpy>=1.23.0"]`). However, only use this when it is necessary so that users aren't restricted to a given version without a valid reason. + +## Python Version + +If you know your code can only run on certain Python versions, you should specify that in the `requires-python` field under the `[project]` header. When in doubt, we recommend setting it to the range of [currently supported Python versions](https://devguide.python.org/versions/#versions) (specifically those with security and bugfix statuses). + +You can also update the listed versions in the `classifiers` field, although this is only for informational purposes. The list of supported Python classifier fields can be found on the corresponding [PyPI page](https://pypi.org/classifiers/). diff --git a/docs/example_docs/intro/resources.md b/docs/example_docs/intro/resources.md new file mode 100644 index 0000000..9c4d43f --- /dev/null +++ b/docs/example_docs/intro/resources.md @@ -0,0 +1,21 @@ +# Resources + +## Software Development + +Looking for external resources to get started with software development? Here are some useful ones: + +- [Scientific Python Development Guide](https://learn.scientific-python.org/development/) +- [Turing Way Guide for Reproducible Research](https://the-turing-way.netlify.app/reproducible-research/reproducible-research.html) +- [Turing Way Guide for Project Design](https://the-turing-way.netlify.app/project-design/project-design.html) + +## Git and Version Control + +For git and version control specifically: + +- [Git Guides](https://github.com/git-guides) +- [Software Carpentry](https://swcarpentry.github.io/git-novice/) + +For GitHub: + +- [GitHub Docs](https://docs.github.com/en/get-started/quickstart/hello-world) +- [GitHub Skills](https://skills.github.com/) diff --git a/docs/example_docs/intro/why.md b/docs/example_docs/intro/why.md new file mode 100644 index 0000000..61e33e8 --- /dev/null +++ b/docs/example_docs/intro/why.md @@ -0,0 +1,26 @@ +# Why? + +## Purpose + +The first question to address is: why? Why use a template repository like this? Why make a Python package at all, as opposed to writing custom scripts or Jupyter Notebooks? + +The answer, in short, is _sustainable and reproducible_ software development. Here are some of the benefits: + +- Your package can be easily installed by others using `pip`. +- Your package can have _automated_ unit tests that run every time you make a commit, making sure you don't accidentally break your own code. +- You can easily make and share documentation with no hassle. +- You will instantly be adopting good programming practices that will help you for life. + +Of course, there are many more reasons, but hopefully that's convincing enough! + +## Alternatives + +This is by no means the only template of its kind. Some alternatives include: + +- [cookiecutter](https://github.com/cookiecutter/cookiecutter) +- [pyscaffold](https://github.com/pyscaffold/pyscaffold) +- [python-package-template](https://github.com/microsoft/python-package-template) + +... and many more. + +Feel free to use them if you wish! This template repository exists because we are all opinionated people, and this template focuses on things that I value most. But the point is to just use something that works well for you. diff --git a/docs/example_docs/media/create_a_repo.png b/docs/example_docs/media/create_a_repo.png new file mode 100644 index 0000000..59d3a82 Binary files /dev/null and b/docs/example_docs/media/create_a_repo.png differ diff --git a/docs/example_docs/media/deploy_docs.png b/docs/example_docs/media/deploy_docs.png new file mode 100644 index 0000000..e788db0 Binary files /dev/null and b/docs/example_docs/media/deploy_docs.png differ diff --git a/docs/example_docs/media/find_replace.png b/docs/example_docs/media/find_replace.png new file mode 100644 index 0000000..7305b1b Binary files /dev/null and b/docs/example_docs/media/find_replace.png differ diff --git a/docs/example_docs/media/gitkraken_clone.png b/docs/example_docs/media/gitkraken_clone.png new file mode 100644 index 0000000..11c508d Binary files /dev/null and b/docs/example_docs/media/gitkraken_clone.png differ diff --git a/docs/example_docs/mkdocs/build.md b/docs/example_docs/mkdocs/build.md new file mode 100644 index 0000000..ae031af --- /dev/null +++ b/docs/example_docs/mkdocs/build.md @@ -0,0 +1,23 @@ +# Building the Docs + +## The `mkdocs.yml` File + +Once you have added your documentation, you will need to update the `/mkdocs.yml` file with information about how you want to arrange the files. Specifically, you will need to update the `nav` secction of the `mkdocs.yml` file to point to all your individual `.md` files, organizing them by category. + +!!! Note + + Keep the `- Code Documentation: reference/` line in the `nav` section of `mkdocs.yml`. It will automatically transform your docstrings into beautiful documentation! The rest of the `nav` items you can replace. + +## The Build Process + +To see how your documentation will look in advance, you can build it locally by running the following command in the base directory: + +```bash +mkdocs serve +``` + +A URL will be printed out that you can open in your browser. + +## Deploying the Docs + +To allow your documentation to be visible via GitHub Pages, go to "Settings > Pages" in your repository's settings and make sure "Branch" is set to "gh-pages" instead of "main". diff --git a/docs/example_docs/mkdocs/docs.md b/docs/example_docs/mkdocs/docs.md new file mode 100644 index 0000000..87ff3d2 --- /dev/null +++ b/docs/example_docs/mkdocs/docs.md @@ -0,0 +1,19 @@ +# Writing the Docs + +## Mkdocs + +Now it's time to write some documentation! This isn't very difficult, and of course you're reading some documentation right now. The documentation is written using markdown, which is the same way GitHub comments are formatted. + +!!! Tip + + Check out the [Markdown Guide](https://www.markdownguide.org/basic-syntax/) for an overview of the basic syntax. + +This template repository uses a documentation format called mkdocs, specifically a useful theme called [Material for Mkdocs](https://squidfunk.github.io/mkdocs-material/). This enables many wonderful goodies like the "tip" callout you see above and much more. + +## Adding Markdown Files + +Your documentation will live in the `/docs` folder. You can think of each markdown (`.md`) file as being a specific page in the documentation, and each folder as being a related collection of pages. The markdown page you are reading right now is found at `/docs/example_docs/mkdocs/docs.md`, for instance. Of course, you will want to replce the `/docs/example_docs` folder with your own documentation. + +!!! Note + + You typically do not need to touch the `/docs/gen_ref_pages.py` script. It is used to automatically build the documentation for your code from its docstrings. diff --git a/docs/example_docs/other/apps.md b/docs/example_docs/other/apps.md new file mode 100644 index 0000000..bfa9064 --- /dev/null +++ b/docs/example_docs/other/apps.md @@ -0,0 +1,9 @@ +# Third-Party Apps + +## Overview + +There are several configuration files provided for popular third-party applications that can be quite useful: + +1. The `/.deepsource.toml` file is a configuration file for the web platform [DeepSource](https://deepsource.com/), which has a useful GitHub extension for cleaning up your code and spotting common errors. +2. The `/.sourcery.yaml` file is a configuration file for [Sourcery](https://sourcery.ai/), which can automatically refactor your code. +3. The `/.codecov.yml` file is a configuration file for [Codecov](https://codecov.io/), which will tell you the fraction of lines covered by your test suite if the GitHub integration is enabled. diff --git a/docs/example_docs/other/linting.md b/docs/example_docs/other/linting.md new file mode 100644 index 0000000..fabcae9 --- /dev/null +++ b/docs/example_docs/other/linting.md @@ -0,0 +1,12 @@ +# Linting and Formatting + +## Overview + +When you installed the `[dev]` dependencies, you installed several code-formatting and linting tools, including: + +1. [`black`](https://black.readthedocs.io/en/stable/): A very useful and opinionated code formatter, which you can use by running `black .` in the base directory. +2. [`isort`](https://pycqa.github.io/isort/): A utility that will sort your import statements for you, which you can use by running `isort .` in the base directory. +3. [`ruff`](https://docs.astral.sh/ruff/): A versatile Python linter to clean up your code, which you can use by running `ruff . --fix` in the base directory. +4. [`docformatter`](https://github.com/PyCQA/docformatter): A simple docstring formatter, which you can use by running `docformatter . -r -i` in the base directory. + +Modifications to the rules these formatters use can be defined in the `pyproject.toml` file, and we have chosen some useful defaults. diff --git a/docs/example_docs/setup/basics.md b/docs/example_docs/setup/basics.md new file mode 100644 index 0000000..a58eb4a --- /dev/null +++ b/docs/example_docs/setup/basics.md @@ -0,0 +1,19 @@ +# Initial Changes + +At this point, you now have your template repository on GitHub and locally on your machine. Now it's time to start making some modifications. + +### README + +The first thing to do is update the README (`/README.md`), which should contain a user-friendly summary of what your package is all about. This can be whatever you want. Feel free to be creative! + +### License + +The template repository comes premade with a sample license (`/LICENSE.md`), in this case the very popular and permissive [BSD 3-Clause license](https://opensource.org/license/bsd-3-clause/). Feel free to change this for your own project or keep it as-is if you don't quite know yet. + +!!! Tip + + There are *many* licenses that one can consider. A comprehensive list can be found on the [Open Source Initiative](https://opensource.org/licenses/?categories=popular-strong-community) website, but a less overwhelming route is to use [choosealicense.com](https://choosealicense.com/). + +### Code of Conduct + +The template repository ships with a premade Code of Conduct (`/CODE_OF_CONDUCT.md`) that is obtained from the [Contributor Covenant](https://www.contributor-covenant.org/). Of course, you can feel free to keep or change this as you see fit, but it is often a good idea to have a code of conduct for public repositories. diff --git a/docs/example_docs/setup/name.md b/docs/example_docs/setup/name.md new file mode 100644 index 0000000..6805c5d --- /dev/null +++ b/docs/example_docs/setup/name.md @@ -0,0 +1,13 @@ +# Updating the Name + +Now for your first major task: **replace all instances of the word "template" with your desired package name**. + +!!! Note + + Don't forget to update the name of the `/src/template` folder, e.g. so that it is of the form `src/`. + +!!! Tip + + If you're using [Visual Studio Code](https://code.visualstudio.com/) as your editor, you can do `ctrl+shift+H` to find-and-replace all instances of "template" with your own package name. + + ![](../media/find_replace.png) diff --git a/docs/example_docs/setup/prep.md b/docs/example_docs/setup/prep.md new file mode 100644 index 0000000..602198a --- /dev/null +++ b/docs/example_docs/setup/prep.md @@ -0,0 +1,39 @@ +# Preparatory Steps + +## Naming Your Package + +So, you have an idea for your own Python package. The first thing you'll need to do is come up with a name! + +!!! Tip + + If you plan on making a Python package that is widely distributed, first check to see if the name already exists on [PyPI](https://pypi.org/). + +## Making a Repository + +With a nice name in mind, [create a new repository](https://github.com/new?template_name=template&template_owner=Quantum-Accelerators) using this template. Give it a name, a description, and decide if you want it to be public or private. + +![](../media/create_a_repo.png) + +## Cloning Your Repository + +You'll now want to [clone the repository](https://github.com/git-guides/git-clone) to your local machine so you can easily make changes. + +### Via a Desktop Client + +You can use a desktop client to interface with GitHub. It is worthwhile to learn how to use such a program for your day-to-day work. + +!!! Tip + + We strongly suggest using [GitKraken](https://www.gitkraken.com/) to interface with git and GitHub. GitKraken Pro is also [free for students](https://help.gitkraken.com/gitkraken-client/gitkraken-edu-pack/). + +![](../media/gitkraken_clone.png) + +### Via the Command Line + +If you prefer, you can clone the repository via the following command in the command-line, provided you have [git](https://git-scm.com/) installed. + +```bash +git clone https://github.com/MyAccountName/MyPackageName +``` + +You can get the URL directly from the GitHub page when you click the green "<> Code" button. diff --git a/docs/gen_ref_pages.py b/docs/gen_ref_pages.py new file mode 100644 index 0000000..3fd78aa --- /dev/null +++ b/docs/gen_ref_pages.py @@ -0,0 +1,34 @@ +""" +Generate the code reference pages and navigation. + +You never have to run this manually! +""" + +from __future__ import annotations + +from pathlib import Path + +import mkdocs_gen_files + +nav = mkdocs_gen_files.Nav() + +for path in sorted(Path("src").rglob("*.py")): + module_path = path.relative_to("src").with_suffix("") + doc_path = path.relative_to("src").with_suffix(".md") + full_doc_path = Path("reference", doc_path) + + parts = tuple(module_path.parts) + + if parts[-1] in ("__init__", "__main__"): + continue + + nav[parts] = doc_path.as_posix() + + with mkdocs_gen_files.open(full_doc_path, "w") as fd: + ident = ".".join(parts) + fd.write(f"::: {ident}") + + mkdocs_gen_files.set_edit_path(full_doc_path, Path("../") / path) + +with mkdocs_gen_files.open("reference/SUMMARY.md", "w") as nav_file: + nav_file.writelines(nav.build_literate_nav()) diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..6553500 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,5 @@ +# template + +Welcome to the documentation for the `template` code! Here you will find everything you need to get started with your own Python package. + +**Check out the corresponding ⭐[YouTube tutorial](https://www.youtube.com/watch?v=th2CqJ6oBuM)⭐ for a video overview!** diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..57db70c --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,122 @@ +site_name: template +site_author: YourName +site_description: >- + This is the template package! +nav: + - Home: index.md + - Overview: + - example_docs/intro/why.md + - example_docs/intro/resources.md + - Setup: + - example_docs/setup/prep.md + - example_docs/setup/name.md + - example_docs/setup/basics.md + - Installation: + - example_docs/installation/pyproject.md + - example_docs/installation/install.md + - Code: + - example_docs/code/source.md + - example_docs/code/hints.md + - example_docs/code/tests.md + - Documentation: + - example_docs/mkdocs/docs.md + - example_docs/mkdocs/build.md + - GitHub and CI: + - example_docs/github/commits.md + - example_docs/github/workflows.md + - Miscellaneous: + - example_docs/other/linting.md + - example_docs/other/apps.md + - Code Documentation: reference/ # keep me! + - About: + - example_docs/about/changelog.md + - example_docs/about/conduct.md + - example_docs/about/license.md + +repo_url: https://github.com/Quantum-Accelerators/template/ +edit_uri: blob/main/docs/ + +theme: + features: + - content.action.edit + - content.code.copy + - content.code.select + - content.code.annotate + - content.tabs.link + - content.tooltips + - navigation.footer + - navigation.path + - navigation.tracking + - navigation.sections + - navigation.top + - search.highlight + - search.suggest + - search.share + - header.autohide + - toc.follow + name: material + palette: + primary: orange + scheme: slate + +markdown_extensions: + - abbr + - admonition + - attr_list + - def_list + - footnotes + - md_in_html + - toc: + permalink: true + - pymdownx.arithmatex: + generic: true + - pymdownx.betterem: + smart_enable: all + - pymdownx.caret + - pymdownx.details + - pymdownx.highlight: + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + - pymdownx.inlinehilite + - pymdownx.keys + - pymdownx.magiclink + - pymdownx.mark + - pymdownx.snippets + - pymdownx.smartsymbols + - pymdownx.superfences: + custom_fences: + - class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format "" + name: mermaid + - pymdownx.tabbed: + alternate_style: true + - pymdownx.tasklist: + custom_checkbox: true + - pymdownx.tilde + +plugins: + - search: + separator: '[\s\-,:!=\[\]()"`/]+|\.(?!\d)|&[lg]t;|(?!\b)(?=[A-Z][a-z])' + - autorefs + - social + - offline + - mkdocstrings: + default_handler: python + handlers: + python: + import: + - https://docs.python.org/3/objects.inv + - https://numpy.org/doc/stable/objects.inv + options: + docstring_style: numpy + docstring_section_style: list + separate_signature: true + merge_init_into_class: true + docstring_options: + ignore_init_summary: true + - gen-files: + scripts: + - docs/gen_ref_pages.py + - literate-nav: + nav_file: SUMMARY.md diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..bacb196 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,157 @@ +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[project] +name = "template" +description="A Python package description goes here." +version = "0.0.1" +readme = "README.md" +license = { text = "BSD-3" } +authors = [{ name = "YourName", email = "YourName@email.com" }] +keywords = ["MyKeyword1", "MyKeyword2"] +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Science/Research", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Intended Audience :: Science/Research", + "Topic :: Scientific/Engineering", + "Operating System :: Microsoft :: Windows", + "Operating System :: Unix", + "Operating System :: MacOS", +] +requires-python = ">=3.9, <3.13" +dependencies = ["numpy"] + +[project.optional-dependencies] +dev = ["pytest>=7.4.0", "pytest-cov>=3.0.0", "ruff>=0.0.285"] +docs = [ + "mkdocs-material>=9.4.0", + "mkdocstrings[python]>=0.22.0", + "mkdocs-gen-files>=0.5.0", + "mkdocs-literate-nav>=0.6.0", + "pillow>=10.0.0", + "cairosvg>=2.7.1" +] + +[project.urls] +repository = "https://github.com/Quantum-Accelerators/template" +documentation = "https://quantum-accelerators.github.io/template/" +changelog = "https://github.com/Quantum-Accelerators/template/blob/main/CHANGELOG.md" + +[tool.setuptools.package-data] +template = ["py.typed"] + +[tool.pyright] +include = ["template"] +exclude = ["**/__pycache__"] + +[tool.pytest.ini_options] +minversion = "6.0" +addopts = ["-p no:warnings", "--import-mode=importlib"] +xfail_strict = true +log_cli_level = "warn" +pythonpath = "src" +testpaths = ["tests"] + +[tool.black] +exclude = ''' +/( + \.git + | \.tox +)/ +''' +skip-magic-trailing-comma = true + +[tool.isort] +profile = 'black' +skip_gitignore = true + +[tool.coverage.run] +source = ["src"] + +[tool.coverage.report] +exclude_also = [ + "if TYPE_CHECKING:", + "if __name__ == .__main__.:", + "except ImportError", +] + +[tool.ruff] +lint.select = [ + "A", # flake8-builtins + "ARG", # flake8-unused-arguments + "ASYNC", # flake8-async + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "DTZ", # flake8-datetimez + "E", # pycodestyle error + "EXE", # flake8-executable + "F", # pyflakes + "FA", # flake8-future-annotations + "FLY", # flynt + "I", # isort + "ICN", # flake8-import-conventions + "INT", # flake8-gettext + "ISC", # flake8-implicit-str-concat + "LOG", # flake8-logging + "NPY", # numpy-specific rules + "PD", # pandas-vet + "PERF", # perflint + "PIE", # flake8-pie + "PL", # pylint + "PT", # flake8-pytest-style + "PTH", # flake8-use-pathlib + "PYI", # flake8-pyi + "Q", # flake8-quotes + "RET", # flake8-return + "RSE", # flake8-raise + "RUF", # Ruff-specific rules + "SIM", # flake8-simplify + "SLOT", # flake8-slots + "T20", # flake8-print + "TCH", # flake8-type-checking + "TID", # flake8-tidy-imports + "TRIO", # flake8-trio + "UP", # pyupgrade + "W", # pycodestyle warning + "YTT", # flake8-2020 +] +lint.ignore = [ + "E501", # Line too long + "ISC001", # single-line-implicit-string-concatenation + "PERF203", # try-except-in-loop + "PLR", # Design related pylint codes + "PT004", # Fixture does not return anything + "PT011", # pytest.raises + "PT012", # pytest.raises + "RET505", # Unnecessary `elif` after `return` +] +src = ["src"] +lint.unfixable = [ + "T20", # Removes print statements + "F841", # Removes unused variables +] +lint.pydocstyle.convention = "numpy" +lint.isort.known-first-party = ["template"] +lint.isort.required-imports = ["from __future__ import annotations"] +extend-include = ["*.ipynb"] + +[tool.ruff.lint.per-file-ignores] +"__init__.py" = ["F401"] +"tests/**" = ["ANN", "ARG", "D", "E402", "PTH", "S101"] + +[tool.docformatter] +pre-summary-newline = true +black = true + +[tool.mypy] +ignore_missing_imports = true +namespace_packages = true +explicit_package_bases = true +no_implicit_optional = false +disable_error_code = "annotation-unchecked" diff --git a/src/template/__init__.py b/src/template/__init__.py new file mode 100644 index 0000000..6234efa --- /dev/null +++ b/src/template/__init__.py @@ -0,0 +1,8 @@ +"""Init data""" + +from __future__ import annotations + +from importlib.metadata import version + +# Load the version +__version__ = version("template") diff --git a/src/template/examples/__init__.py b/src/template/examples/__init__.py new file mode 100644 index 0000000..e7b0521 --- /dev/null +++ b/src/template/examples/__init__.py @@ -0,0 +1 @@ +"""Example functions""" diff --git a/src/template/examples/sample.py b/src/template/examples/sample.py new file mode 100644 index 0000000..7407e1f --- /dev/null +++ b/src/template/examples/sample.py @@ -0,0 +1,68 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +import numpy as np + +if TYPE_CHECKING: + from numpy.typing import NDArray + + +def add(a: float, b: float) -> float: + """ + A function that adds two numbers. + + Parameters + ---------- + a + First number to add. + b + Second number to add. + + Returns + ------- + float + The sum of a and b. + """ + return a + b + + +def divide(a: float, b: float) -> float: + """ + A function that divides two numbers, i.e. a/b. + + Parameters + ---------- + a + The numerator + b + The denominator + + Returns + ------- + float + The value for a/b + """ + if b == 0: + raise ValueError("Uh oh! The value for b should not be 0.") + + return a / b + + +def make_array(val: float, length: int = 3) -> NDArray: + """ + A function to transform a number into a numpy array. + + Parameters + ---------- + val + Number to turn into an array. + length + The length of the array. + + Returns + ------- + NDArray + An array composed of `val`. + """ + return np.array([val] * length) diff --git a/src/template/py.typed b/src/template/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/tests/examples/test_sample.py b/tests/examples/test_sample.py new file mode 100644 index 0000000..cca9731 --- /dev/null +++ b/tests/examples/test_sample.py @@ -0,0 +1,42 @@ +from __future__ import annotations + +import numpy as np +import pytest +from numpy.testing import assert_allclose, assert_array_equal + +from template.examples.sample import add, divide, make_array + + +def test_add(): + """ + Test that the addition function works. + """ + assert add(1, 2) == 3 + + +def test_divide(): + """ + Test that the division function works. + + Note that we use `pytest.approx()` here since 3/2 != 1.5 + exactly due to floating point precision differences. + + Also note that we can test that a given error is raised + by using `pytest.raises()`. + """ + assert divide(3, 2) == pytest.approx(1.5) + + with pytest.raises(ValueError): + divide(10, 0) + + +def test_make_array(): + """ + Test that the array generation works. + + Note that we use `assert_array_equal` to assert that two + numpy arrays are the same. We also use `assert_allclose()`, + which is effectively an element-wise call to `pytest.approx()`. + """ + assert_array_equal(make_array(3), np.array([3, 3, 3])) + assert_allclose(make_array(divide(3, 2), length=4), np.array([1.5, 1.5, 1.5, 1.5])) diff --git a/tests/requirements.txt b/tests/requirements.txt new file mode 100644 index 0000000..2c04913 --- /dev/null +++ b/tests/requirements.txt @@ -0,0 +1 @@ +numpy==1.26.4