diff --git a/.darglint b/.darglint new file mode 100644 index 0000000..355d713 --- /dev/null +++ b/.darglint @@ -0,0 +1,3 @@ +[darglint] + +docstring_style=google diff --git a/.env b/.env new file mode 100644 index 0000000..e69de29 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..33dc80b --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,47 @@ +name: Publish Python Package + +on: + release: + types: [created] + workflow_dispatch: + +permissions: + contents: read + id-token: write + +concurrency: + group: "publish" + cancel-in-progress: true + +jobs: + publish: + timeout-minutes: 10 + name: Build and publish + + # We don't need to run on all platforms since this package is + # platform-agnostic. The output wheel is something like + # "monotonic_attention--py3-none-any.whl". + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.10" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build wheel + + - name: Build package + run: python -m build --sdist --wheel --outdir dist/ . + + - name: Publish package + uses: pypa/gh-action-pypi-publish@release/v1 + # with: + # user: __token__ + # password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..04aa24d --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,65 @@ +name: Python Checks + +on: + push: + branches: + - master + pull_request: + branches: + - master + types: + - opened + - reopened + - synchronize + - ready_for_review + +concurrency: + group: tests-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + run-base-tests: + timeout-minutes: 10 + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.11" + + - name: Restore cache + id: restore-cache + uses: actions/cache/restore@v3 + with: + path: | + ${{ env.pythonLocation }} + .mypy_cache/ + key: python-requirements-${{ env.pythonLocation }}-${{ github.event.pull_request.base.sha || github.sha }} + restore-keys: | + python-requirements-${{ env.pythonLocation }} + python-requirements- + + - name: Install package + run: | + pip install --upgrade --upgrade-strategy eager --extra-index-url https://download.pytorch.org/whl/cpu -e '.[dev]' + + - name: Run static checks + run: | + mkdir -p .mypy_cache + make static-checks + + - name: Run unit tests + run: | + make test + + - name: Save cache + uses: actions/cache/save@v3 + if: github.ref == 'refs/heads/master' + with: + path: | + ${{ env.pythonLocation }} + .mypy_cache/ + key: ${{ steps.restore-cache.outputs.cache-primary-key }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ae8e28a --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# .gitignore + +# Python +*.py[oc] +__pycache__/ +*.egg-info +.eggs/ +.mypy_cache/* +.pyre/ +.pytest_cache/ +.ruff_cache/ +.dmypy.json + +# Databases +*.db + +# Build artifacts +build/ +dist/ +*.so +out*/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3efb886 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Benjamin Bolte + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..b2e8e40 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +recursive-include photolingo/ *.py *.txt py.typed MANIFEST.in diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2b48dac --- /dev/null +++ b/Makefile @@ -0,0 +1,77 @@ +# Makefile + +define HELP_MESSAGE +photolingo + +# Installing + +1. Create a new Conda environment: `conda create --name photolingo python=3.11` +2. Activate the environment: `conda activate photolingo` +3. Install the package: `make install-dev` + +# Running Tests + +1. Run autoformatting: `make format` +2. Run static checks: `make static-checks` +3. Run unit tests: `make test` + +endef +export HELP_MESSAGE + +all: + @echo "$$HELP_MESSAGE" +.PHONY: all + +# ------------------------ # +# Build # +# ------------------------ # + +install: + @pip install --verbose -e . +.PHONY: install + +install-dev: + @pip install --verbose -e '.[dev]' +.PHONY: install + +build-ext: + @python setup.py build_ext --inplace +.PHONY: build-ext + +clean: + rm -rf build dist *.so **/*.so **/*.pyi **/*.pyc **/*.pyd **/*.pyo **/__pycache__ *.egg-info .eggs/ .ruff_cache/ +.PHONY: clean + +# ------------------------ # +# Static Checks # +# ------------------------ # + +py-files := $(shell find . -name '*.py') + +format: + @black $(py-files) + @ruff format $(py-files) +.PHONY: format + +format-cpp: + @clang-format -i $(shell find . -name '*.cpp' -o -name '*.h') + @cmake-format -i $(shell find . -name 'CMakeLists.txt' -o -name '*.cmake') +.PHONY: format-cpp + +static-checks: + @black --diff --check $(py-files) + @ruff check $(py-files) + @mypy --install-types --non-interactive $(py-files) +.PHONY: lint + +mypy-daemon: + @dmypy run -- $(py-files) +.PHONY: mypy-daemon + +# ------------------------ # +# Unit tests # +# ------------------------ # + +test: + python -m pytest +.PHONY: test diff --git a/README.md b/README.md new file mode 100644 index 0000000..90d581b --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# photolingo diff --git a/photolingo/__init__.py b/photolingo/__init__.py new file mode 100644 index 0000000..f102a9c --- /dev/null +++ b/photolingo/__init__.py @@ -0,0 +1 @@ +__version__ = "0.0.1" diff --git a/photolingo/py.typed b/photolingo/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/photolingo/requirements-dev.txt b/photolingo/requirements-dev.txt new file mode 100644 index 0000000..e36b648 --- /dev/null +++ b/photolingo/requirements-dev.txt @@ -0,0 +1,7 @@ +# requirements-dev.txt + +black +darglint +mypy +pytest +ruff diff --git a/photolingo/requirements.txt b/photolingo/requirements.txt new file mode 100644 index 0000000..2ee1e7b --- /dev/null +++ b/photolingo/requirements.txt @@ -0,0 +1,2 @@ +# requirements.txt + diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..ee1d1ba --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,72 @@ +[tool.black] + +line-length = 120 +target-version = ["py311"] +include = '\.pyi?$' + +[tool.pytest.ini_options] + +addopts = "-rx -rf -x -q --full-trace" +testpaths = ["tests"] + +markers = [ + "slow: Marks test as being slow", +] + +[tool.mypy] + +pretty = true +show_column_numbers = true +show_error_context = true +show_error_codes = true +show_traceback = true +disallow_untyped_defs = true +strict_equality = true +allow_redefinition = true + +warn_unused_ignores = true +warn_redundant_casts = true + +incremental = true +namespace_packages = false + +# Uncomment to exclude modules from Mypy. +# [[tool.mypy.overrides]] +# module = [] +# ignore_missing_imports = true + +[tool.isort] + +profile = "black" + +[tool.ruff] + +line-length = 120 +target-version = "py310" + +[tool.ruff.lint] + +select = ["ANN", "D", "E", "F", "I", "N", "PGH", "PLC", "PLE", "PLR", "PLW", "W"] + +ignore = [ + "ANN101", "ANN102", + "D101", "D102", "D103", "D104", "D105", "D106", "D107", + "N812", "N817", + "PLR0911", "PLR0912", "PLR0913", "PLR0915", "PLR2004", + "PLW0603", "PLW2901", +] + +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[tool.ruff.lint.per-file-ignores] + +"__init__.py" = ["E402", "F401", "F403", "F811"] + +[tool.ruff.lint.isort] + +known-first-party = ["photolingo", "tests"] +combine-as-imports = true + +[tool.ruff.lint.pydocstyle] + +convention = "google" diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..05d396d --- /dev/null +++ b/setup.cfg @@ -0,0 +1,8 @@ +[options] + +packages = find: + +[options.packages.find] + +exclude = + tests diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..fbd5522 --- /dev/null +++ b/setup.py @@ -0,0 +1,39 @@ +# mypy: disable-error-code="import-untyped" +#!/usr/bin/env python +"""Setup script for the project.""" + +import re + +from setuptools import setup + +with open("README.md", "r", encoding="utf-8") as f: + long_description: str = f.read() + + +with open("photolingo/requirements.txt", "r", encoding="utf-8") as f: + requirements: list[str] = f.read().splitlines() + + +with open("photolingo/requirements-dev.txt", "r", encoding="utf-8") as f: + requirements_dev: list[str] = f.read().splitlines() + + +with open("photolingo/__init__.py", "r", encoding="utf-8") as fh: + version_re = re.search(r"^__version__ = \"([^\"]*)\"", fh.read(), re.MULTILINE) +assert version_re is not None, "Could not find version in photolingo/__init__.py" +version: str = version_re.group(1) + + +setup( + name="photolingo", + version=version, + description="The photolingo project", + author="Benjamin Bolte", + url="https://github.com/kscalelabs/photolingo", + long_description=long_description, + long_description_content_type="text/markdown", + python_requires=">=3.11", + install_requires=requirements, + tests_require=requirements_dev, + extras_require={"dev": requirements_dev}, +) diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..fdc62be --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,15 @@ +"""Defines PyTest configuration for the project.""" + +import random + +import pytest +from _pytest.python import Function + + +@pytest.fixture(autouse=True) +def set_random_seed() -> None: + random.seed(1337) + + +def pytest_collection_modifyitems(items: list[Function]) -> None: + items.sort(key=lambda x: x.get_closest_marker("slow") is not None) diff --git a/tests/test_dummy.py b/tests/test_dummy.py new file mode 100644 index 0000000..284dabe --- /dev/null +++ b/tests/test_dummy.py @@ -0,0 +1,12 @@ +"""Defines a dummy test.""" + +import pytest + + +def test_dummy() -> None: + assert True + + +@pytest.mark.slow +def test_slow() -> None: + assert True