diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..65e338d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +__pycache__ +*.pyc +*.pyo +*.pyd +*.egg-info +dist +build diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..36094c1 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,17 @@ +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "weekly" + commit-message: + prefix: "chore" + include: "scope" + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/pre-commit-au.yml b/.github/workflows/pre-commit-au.yml new file mode 100644 index 0000000..f7a86fc --- /dev/null +++ b/.github/workflows/pre-commit-au.yml @@ -0,0 +1,21 @@ +name: Pre-commit auto-update + +on: + # every day at midnight + schedule: + - cron: "0 0 * * *" + # on demand + workflow_dispatch: + +jobs: + upgrade: + uses: browniebroke/github-actions/.github/workflows/pre-commit-autoupdate.yml@v1 + secrets: + gh_pat: ${{ secrets.GITHUB_TOKEN }} + with: + # Inputs listed with their default (all optional) + config_path: ".pre-commit-config.yaml" # path is relative to repository root + python_version: "3.11" + branch_name: "update/pre-commit-hooks" + pull_request_title: "chore(deps): upgrade pre-commit dependencies" + commit_message: "chore(deps): upgrade pre-commit dependencies" diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml new file mode 100644 index 0000000..754213d --- /dev/null +++ b/.github/workflows/python-app.yml @@ -0,0 +1,56 @@ +name: Python application + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + merge_group: + types: [checks_requested] + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.11", "3.12"] + + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + cache: 'pip' + - name: Install dependencies + run: | + python -m pip install --upgrade pip setuptools + python -m pip install -r requirements.txt + python -m pip install -e . + - name: Pre-commit checks + run: | + python -m pip install pre-commit + pre-commit run --all-files + - name: Run tests + run: | + python -m pytest + - name: Sphinx documentation build + run: | + make docs + - name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@v3 + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} + with: + publish_branch: gh-pages + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: docs/build/html/ + force_orphan: true + + # TODO in the future + # - name: Build and push Docker image + # uses: docker/build-push-action@v2 + # with: + # context: . + # push: true + # tags: nathfitz/test:latest diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5decde7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +# qtmlib/.gitignore file contents +__pycache__/ +*.pyc +*.pyo +*.egg-info/ +dist/ +build/ +.idea/ +.vscode/ +__pypackages__/ +*.log +*.swp +.DS_Store +.qtmlib/ +*.venv/ + +# autogenerated by sphinx-napoleon +docs/source/modules.rst +docs/source/pytemplate.rst diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..225db69 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,35 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-toml + - id: check-yaml + - id: check-added-large-files + # Python-specific + - id: check-ast + - id: check-docstring-first + - id: debug-statements + + - repo: https://github.com/crate-ci/typos + rev: v1.16.23 + hooks: + - id: typos + args: [] + + - repo: https://github.com/psf/black + rev: 23.11.0 + hooks: + - id: black + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.1.7 + hooks: + - id: ruff + args: [--fix, --exit-non-zero-on-fix] + + - repo: https://github.com/RobertCraigie/pyright-python + rev: v1.1.339 + hooks: + - id: pyright diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..cb7fd19 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3.11-slim-buster + +# Set the working directory to /pytemplate +WORKDIR /pytemplate + +# Copy the pyproject.toml and requirements.txt files to the container +COPY . . + +# Install the dependencies +RUN pip install --no-cache-dir -r requirements.txt + +# Start the web server +CMD ["python", "-m", "pytemplate.main"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..fd8b6af --- /dev/null +++ b/Makefile @@ -0,0 +1,23 @@ +.PHONY: install dev tests lint docs clean build + +install: + pip install . + +dev: + pip install -e . + +tests: + pytest . + +lint: + pre-commit run --all-files + +docs: + sphinx-apidoc -f -o docs/source/ pytemplate + sphinx-build -M html docs/source/ docs/build/ + +clean: + rm -rf *.egg-info dist build docs/build + +build: clean + python -m build --sdist -n diff --git a/README.md b/README.md new file mode 100644 index 0000000..9d91f79 --- /dev/null +++ b/README.md @@ -0,0 +1,75 @@ +# pytemplate + +This is a Python 3.11 app called pytemplate. The project includes Docker, Pyright, Ruff, GitHub Actions, Black, pre-commit, and Sphinx. + +## Project Structure + +The project structure is as follows: + +```sh +pytemplate +├── .dockerignore +├── .github +│   └── workflows +│   └── python-app.yml +├── .gitignore +├── .pre-commit-config.yaml +├── Dockerfile +├── Makefile +├── README.md +├── docs +│   ├── Makefile +│   └── source +│   ├── conf.py +│   └── index.rst +├── pyproject.toml +├── pytemplate +│   ├── __init__.py +│   ├── main.py +│   └── utils.py +├── requirements.txt +├── ruff.toml +└── tests + ├── test_main.py + └── test_utils.py +``` + +The source code is located in the `pytemplate` folder, which contains the `__init__.py`, `main.py`, and `utils.py` files. The tests are located in the `tests` folder, which contains the `test_main.py` and `test_utils.py` files. + +The project uses toml for configuration instead of `setup.py`. The configuration file is located in `pyproject.toml`. + +The project includes Docker, with a `Dockerfile` located in the root directory. The `.dockerignore` file is also located in the root directory. + +The project includes Pyright for static type checking, pre-commit for code formatting, Black for code formatting and Ruff for linting. The configuration for these tools is located in the `.pre-commit-config.yaml` and `ruff.toml` files. + +The project includes Sphinx for documentation, with the documentation located in the `docs` folder. The `source/conf.py` file contains the configuration for Sphinx. + +The project includes GitHub Actions for continuous integration, with the configuration located in the `.github/workflows/python-app.yml` file. + +## Usage Notes + +[Replace](https://github.com/your-tools/ruplacer) all mentions of "pytemplate" to your own project's name. + +## Installation + +To install the project, clone the repository and run: + +```sh +python -m venv .venv +source .venv/bin/activate +pip install -U pip setuptools +pip install -r requirements.txt +pre-commit install +``` + +Then install the project using: + +```sh +pip install -e . +``` + +See `Makefile` for other useful commands. + +## Testing + +Just issue `pytest` from the root directory. diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d0c3cbf --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..c52578f --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,37 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. +import pathlib +import sys + +sys.path.insert(0, pathlib.Path(__file__).parents[2].resolve().as_posix()) + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = "pytemplate" +project_copyright = "2023, Author" +author = "Author" +release = "0.0.1" + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = [ + "sphinx.ext.doctest", + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.napoleon", +] + +exclude_patterns = [] + + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = "alabaster" diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..43838a6 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,51 @@ +pytemplate +========== + +Demo Sphinx site for the `CQCL pytemplate `_ project. + +---- + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + modules + +Installation +------------ + +To install pytemplate, simply run: + +.. code-block:: bash + + pip install pytemplate + +Usage +----- + +To use pytemplate, import the main module: + +.. code-block:: python + + from pytemplate import main + + main.run() + +Utils +----- + +The utils module contains various utility functions that can be used in conjunction with pytemplate. To use the utils module, import it like so: + +.. code-block:: python + + from pytemplate import utils + + utils.do_something() + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..445e33e --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,29 @@ +[build-system] +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "pytemplate" +version = "0.0.1" +description = "A python library" +readme = "README.md" +requires-python = ">=3.11" +authors = [{name = "Author", email = "author@email.com" }] + +[tool.setuptools.packages.find] +where = ["."] + +[project.urls] +Repository = "https://github.com/CQCL/pytemplate.git" + +# See https://microsoft.github.io/pyright/#/getting-started +[tool.pyright] +include = ["pytemplate","tests"] +ignore = ["**/*.ipynb"] +pythonVersion = "3.11" +typeCheckingMode = "strict" + +[tool.pytest.ini_options] +pythonpath = [ + "." +] diff --git a/pytemplate/__init__.py b/pytemplate/__init__.py new file mode 100644 index 0000000..b189ab0 --- /dev/null +++ b/pytemplate/__init__.py @@ -0,0 +1 @@ +"""Init file for pytemplate.""" diff --git a/pytemplate/main.py b/pytemplate/main.py new file mode 100644 index 0000000..dfe19e8 --- /dev/null +++ b/pytemplate/main.py @@ -0,0 +1,9 @@ +"""This is the main module of the pytemplate package.""" + + +def hello_world() -> str: + """Print 'Hello, world!' to the console.""" + return "Hello, World!" + + +print(hello_world()) diff --git a/pytemplate/utils.py b/pytemplate/utils.py new file mode 100644 index 0000000..2cca870 --- /dev/null +++ b/pytemplate/utils.py @@ -0,0 +1,6 @@ +"""Utility functions for the pytemplate package.""" + + +def add_numbers(a: int, b: int) -> int: + """Add two numbers and returns the result.""" + return a + b diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..5bbc5d8 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +black==23.11.0 +build==1.0.3 +pre-commit==3.5.0 +pyright==1.1.338 +pytest==7.4.3 +sphinx==7.2.6 +wheel==0.42.0 diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000..14c2c7b --- /dev/null +++ b/ruff.toml @@ -0,0 +1,76 @@ +# See https://docs.astral.sh/ruff/rules/ +target-version = "py311" + +line-length = 88 + +select = [ + "E", # pycodestyle Errors + "W", # pycodestyle Warnings + + "A", # flake8-builtins + "ANN", # flake8-annotations + "ARG", # flake8-unused-arguments + "B", # flake8-Bugbear + "BLE", # flake8-blind-except + "C4", # flake8-comprehensions + "C90", # mccabe + "COM", # flake8-commas + "D", # pydocstyle + "EM", # flake8-errmsg + "ERA", # eradicate + "EXE", # flake8-executable + "F", # pyFlakes + "FA", # flake8-future-annotations + "FBT", # flake8-boolean-trap + "FIX", # flake8-fixme + "FLY", # flynt + # "FURB", # refurb + "G", # flake8-logging-format + "I", # isort + "ICN", # flake8-import-conventions + "INP", # flake8-no-pep420 + "INT", # flake8-gettext + "ISC", # flake8-implicit-str-concat + # "LOG", # flake8-logging + "N", # pep8-Naming + "NPY", # NumPy-specific + "PERF", # Perflint + "PGH", # pygrep-hooks + "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 + "S", # flake8-bandit (Security) + "SIM", # flake8-simplify + "SLF", # flake8-self + "SLOT", # flake8-slots + "T10", # flake8-debugger + # "T20", # flake8-print + "TCH", # flake8-type-checking + "TD", # flake8-todos + "TID", # flake8-tidy-imports + "TRY", # tryceratops + "UP", # pyupgrade + "YTT", # flake8-2020 +] + +[per-file-ignores] +"__init__.py" = ["F401"] # module imported but unused +"docs/*" = [ + "D100", # Missing docstring in public module + "INP001", # File * is part of an implicit namespace package. Add an `__init__.py`. +] +"tests/*" = [ + "INP001", + "ANN201", # Missing return type annotation for public function + "S101", # Use of `assert` detected + "PLR2004", # Magic value used in comparison, consider replacing * with a constant variable + ] + +[pydocstyle] +convention = "google" diff --git a/tests/test_main.py b/tests/test_main.py new file mode 100644 index 0000000..c24e54c --- /dev/null +++ b/tests/test_main.py @@ -0,0 +1,8 @@ +"""Tests for pytemplate.main module.""" + +from pytemplate.main import hello_world + + +def test_hello_world(): + """Test the hello_world function.""" + assert hello_world() == "Hello, World!" diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..178ff04 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,10 @@ +"""Tests for pytemplate.utils.""" + +from pytemplate.utils import add_numbers + + +def test_add(): + """Test the add function.""" + assert add_numbers(2, 3) == 5 + assert add_numbers(0, 0) == 0 + assert add_numbers(-1, 1) == 0