diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 000000000..0a1eb0c8e --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,14 @@ +name: pre-commit + +on: + pull_request: + push: + branches: [master] + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + - uses: pre-commit/action@v3.0.1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..ee8cdc3a0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,138 @@ +# LINT.IfChange +# Byte-compiled / optimized / DLL files +__pycache__/ +**/*.py[cod] +**/*$py.class + +# C extensions +**/*.so + +# Distribution / packaging +.Python +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don’t work, or not +# install all needed dependencies. +#Pipfile.lock + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# Intellij project settings +.idea + +# VSCode project settings +.vscode + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# Bazel generated files +bazel-* +**/*_pb2.py +**/*_pb2_grpc.py +# LINT.ThenChange(.dockerignore) + +MODULE.bazel +MODULE.bazel.lock diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..40612ac57 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,37 @@ +# pre-commit is a tool to perform a predefined set of tasks manually and/or +# automatically before git commits are made. +# +# Config reference: https://pre-commit.com/#pre-commit-configyaml---top-level +# +# Common tasks +# +# - Register git hooks: pre-commit install +# - Run on all files: pre-commit run --all-files +# +# These pre-commit hooks are run as CI. +# +# NOTE: if it can be avoided, add configs/args in pyproject.toml or below instead of creating a new `.config.file`. +# https://pre-commit.ci/#configuration +ci: + autoupdate_schedule: monthly + autofix_commit_msg: | + [pre-commit.ci] Apply automatic pre-commit fixes + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: end-of-file-fixer + exclude: '\.svg$' + - id: trailing-whitespace + exclude: '\.svg$' + - id: check-json + - id: check-yaml + args: [--allow-multiple-documents, --unsafe] + - id: check-toml + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.5.6 + hooks: + - id: ruff + args: ["--fix"] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ae319c70a..c864d0957 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,3 +21,36 @@ All submissions, including submissions by project members, require review. We use GitHub pull requests for this purpose. Consult [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more information on using pull requests. + +## Using `pre-commit` + +To enforce style and coding standards, this repository uses +[`pre-commit`](https://pre-commit.com/). To get started: + +```shell +pip install pre-commit +pre-commit install +``` + +### Run pre-commit on all files + +By default, `pre-commit` will run its checks on files that have been modified in +a commit. To instead run it on all files, use this command: + +```console +$ pre-commit run --all-files + +# Alternatively +$ pre-commit run -a +``` + +### Skip the pre-commit checks + +Run the following command: + +```console +$ git commit --no-verify +``` + +Be aware the pre-commit checks are run on CI, so any violations will need to be +fixed before code gets merged. diff --git a/pyproject.toml b/pyproject.toml index 459a7fe07..f7b636d33 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,3 +19,101 @@ requires = [ # Required for using org_tensorflow bazel repository. "numpy~=1.22.0", ] + +[tool.ruff] +line-length = 88 + +[tool.ruff.lint] +select = [ + # pycodestyle + "E", + "W", + # Pyflakes + "F", + # pyupgrade + "UP", + # flake8-bugbear + "B", + # flake8-simplify + "SIM", + # isort + "I", + # pep8 naming + "N", + # pydocstyle + "D", + # annotations + "ANN", + # debugger + "T10", + # flake8-pytest + "PT", + # flake8-return + "RET", + # flake8-unused-arguments + "ARG", + # flake8-fixme + "FIX", + # flake8-eradicate + "ERA", + # pandas-vet + "PD", + # numpy-specific rules + "NPY", +] +ignore = [ + "D104", # Missing docstring in public package + "D100", # Missing docstring in public module + "D211", # No blank line before class + "D213", # Multiline summary second line + "PD901", # Avoid using 'df' for pandas dataframes. Perfectly fine in functions with limited scope + "ANN201", # Missing return type annotation for public function (makes no sense for NoneType return types...) + "ANN101", # Missing type annotation for `self` + "ANN204", # Missing return type annotation for special method + "ANN002", # Missing type annotation for `*args` + "ANN003", # Missing type annotation for `**kwargs` + "D105", # Missing docstring in magic method + "D203", # 1 blank line before after class docstring + "D204", # 1 blank line required after class docstring + "D413", # 1 blank line after parameters + "SIM108", # Simplify if/else to one line; not always clearer + "D206", # Advised to disable by ruff-format + "E501", # Advised to disable by ruff-format + "W191", # Advised to disable by ruff-format + "N802", # Function name should be lowercase; unittest uses mixed case + "D107", # Missing docstring in `__init__` + + # These are issues which remain to be fixed + "D102", # Missing docstring in public method + "D103", # Missing docstring in public function + "D401", # First line of docstring should be in imperative mood + "D404", # First word of the docstring should not be "This" + "ANN001", # Missing type annotation for function argument + "ANN202", # Missing return type for private function + "B024", # is an abstract base class, but it has no abstract methods + "FIX002", # Line contains TODO, consider resolving the issue + "ANN401", # Dynamically typed expressions (typing.Any) are disallowed + "UP008", # Use `super()` instead of `super(__class__, self)` + "SIM102", # Use a single `if` statement instead of nested `if` statements +] + +[tool.ruff.lint.per-file-ignores] +"__init__.py" = [ + "F401", # Unused import +] +"*_test.py" = [ + "ANN001", # Type annotations aren't needed for tests; these are fixtures or parametrizations + "PT009", # Use a regular `assert` instead of a unittest-style `assertEqual` + "PT027", # Use `pytest.raises` instead of unittest-style `assertRaisesRegex` + + # Missing docstrings; probably want to fill these out for tests. For now, we just disable + "D101", # Missing docstring in public class + "D102", # Missing docstring in public method + +] + +[tool.ruff.lint.pydocstyle] +convention = "google" + +[tool.isort] +profile = "black" diff --git a/setup.py b/setup.py index 51982f0bd..92e896c89 100644 --- a/setup.py +++ b/setup.py @@ -13,23 +13,21 @@ # limitations under the License. """Package Setup script for ML Metadata.""" -import os +import os # noqa: I001 import platform import shutil import subprocess import sys import setuptools -from setuptools import find_packages -from setuptools import setup +from setuptools import find_packages, setup from setuptools.command.install import install from setuptools.dist import Distribution -# pylint: disable=g-bad-import-order + # It is recommended to import setuptools prior to importing distutils to avoid # using legacy behavior from distutils. # https://setuptools.readthedocs.io/en/latest/history.html#v48-0-0 from distutils.command import build -# pylint: enable=g-bad-import-order _IS_PY311 = sys.version_info >= (3, 11) @@ -166,6 +164,9 @@ def run(self): f'protobuf>={"4.25.2" if _IS_PY311 else "3.20.3"},<5', 'six>=1.10,<2', ], + extras_require={ + 'lint': ['pre-commit'], + }, python_requires='>=3.9,<4', packages=find_packages(), include_package_data=True,