diff --git a/.gitignore b/.gitignore index 0687299..13eb353 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .cache/ +poetry.toml **/__pycache__/ .venv/ diff --git a/README.md b/README.md index 3892c23..12abf56 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,9 @@ The goal of the `lalib` project is to create by reading and writing code. +[](https://github.com/psf/black) + + ## Contributing & Development This project is open for any kind of contribution, @@ -65,6 +68,21 @@ To execute all default tasks, simply invoke: `nox` +#### Code Formatting + +We follow [Google's Python style guide](https://google.github.io/styleguide/pyguide.html). + +During development, + `nox -s format` may be helpful. +It can be speed up by re-using a previously created environment + with the `-R` flag. + +This task formats all source code files with + [autoflake](https://pypi.org/project/autoflake/), + [black](https://pypi.org/project/black/), and + [isort](https://pypi.org/project/isort/). + + ### Branching Strategy The branches in this repository follow the diff --git a/noxfile.py b/noxfile.py index 64333e7..087f97a 100644 --- a/noxfile.py +++ b/noxfile.py @@ -1,6 +1,9 @@ """Maintenance tasks run in isolated environments.""" import collections +import pathlib +import re +import tempfile from collections.abc import Mapping from typing import Any @@ -12,6 +15,9 @@ try: from nox_poetry import session as nox_session except ImportError: nox_session = nox.session + nox_poetry_available = False +else: + nox_poetry_available = True def nested_defaultdict() -> collections.defaultdict[str, Any]: @@ -67,10 +73,30 @@ nox.options.envdir = ".cache/nox" nox.options.error_on_external_run = True # only `git` and `poetry` are external nox.options.reuse_venv = "no" nox.options.sessions = ( # run by default when invoking `nox` on the CLI + "format", ) nox.options.stop_on_first_error = True +@nox_session(name="format", python=MAIN_PYTHON) +def format_(session: nox.Session) -> None: + """Format source files with `autoflake`, `black`, and `isort`.""" + start(session) + + install_pinned(session, "autoflake", "black", "isort") + + locations = session.posargs or SRC_LOCATIONS + + session.run("autoflake", "--version") + session.run("autoflake", *locations) + + session.run("black", "--version") + session.run("black", *locations) + + session.run("isort", "--version-number") + session.run("isort", *locations) + + def start(session: nox.Session) -> None: """Show generic info about a session.""" if session.posargs: @@ -83,10 +109,63 @@ def start(session: nox.Session) -> None: session.run("python", "-c", "import os; print(os.getcwd())") session.run("python", "-c", 'import os; print(os.environ["PATH"])') + session.env["BLACK_CACHE_DIR"] = ".cache/black" session.env["PIP_CACHE_DIR"] = ".cache/pip" session.env["PIP_DISABLE_PIP_VERSION_CHECK"] = "true" +def install_pinned( + session: nox.Session, + *packages_or_pip_args: str, + **kwargs: Any, +) -> None: + """Install packages respecting the "poetry.lock" file. + + Wraps `nox.sessions.Session.install()` such that it installs + packages respecting the pinned versions specified in poetry's + lock file. This makes nox sessions more deterministic. + """ + session.debug("Install packages respecting the poetry.lock file") + + session.run( # temporary fix to avoid poetry's future warning + "poetry", + "config", + "--local", + "warnings.export", + "false", + external=True, + log=False, # because it's just a fix + ) + + if nox_poetry_available: + session.install(*packages_or_pip_args, **kwargs) + return + + with tempfile.NamedTemporaryFile() as requirements_txt: + session.run( + "poetry", + "export", + "--format=requirements.txt", + f"--output={requirements_txt.name}", + "--with=dev", + "--without-hashes", + external=True, + ) + + # `pip install --constraint ...` raises an error if the + # dependencies in requirements.txt contain "extras" + # => Strip "package[extras]==1.2.3" into "package==1.2.3" + dependencies = pathlib.Path(requirements_txt.name).read_text().split("\n") + dependencies = [re.sub(r"\[.*\]==", "==", dep) for dep in dependencies] + pathlib.Path(requirements_txt.name).write_text("\n".join(dependencies)) + + session.install( + f"--constraint={requirements_txt.name}", + *packages_or_pip_args, + **kwargs, + ) + + if MAIN_PYTHON not in SUPPORTED_PYTHONS: msg = f"MAIN_PYTHON version, v{MAIN_PYTHON}, is not in SUPPORTED_PYTHONS" raise RuntimeError(msg) diff --git a/poetry.lock b/poetry.lock index 2f7ba0f..a5994bb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,6 +1,186 @@ -package = [] +[[package]] +name = "autoflake" +version = "2.3.1" +description = "Removes unused imports and unused variables" +optional = false +python-versions = ">=3.8" +files = [ + {file = "autoflake-2.3.1-py3-none-any.whl", hash = "sha256:3ae7495db9084b7b32818b4140e6dc4fc280b712fb414f5b8fe57b0a8e85a840"}, + {file = "autoflake-2.3.1.tar.gz", hash = "sha256:c98b75dc5b0a86459c4f01a1d32ac7eb4338ec4317a4469515ff1e687ecd909e"}, +] + +[package.dependencies] +pyflakes = ">=3.0.0" +tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} + +[[package]] +name = "black" +version = "24.8.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"}, + {file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"}, + {file = "black-24.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42"}, + {file = "black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a"}, + {file = "black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1"}, + {file = "black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af"}, + {file = "black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4"}, + {file = "black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af"}, + {file = "black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368"}, + {file = "black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed"}, + {file = "black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018"}, + {file = "black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2"}, + {file = "black-24.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd"}, + {file = "black-24.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2"}, + {file = "black-24.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e"}, + {file = "black-24.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920"}, + {file = "black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c"}, + {file = "black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e"}, + {file = "black-24.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47"}, + {file = "black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb"}, + {file = "black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed"}, + {file = "black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "isort" +version = "5.13.2" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, + {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, +] + +[package.extras] +colors = ["colorama (>=0.4.6)"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "platformdirs" +version = "4.3.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.3.2-py3-none-any.whl", hash = "sha256:eb1c8582560b34ed4ba105009a4badf7f6f85768b30126f351328507b2beb617"}, + {file = "platformdirs-4.3.2.tar.gz", hash = "sha256:9e5e27a08aa095dd127b9f2e764d74254f482fef22b0970773bfba79d091ab8c"}, +] + +[package.extras] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.11.2)"] + +[[package]] +name = "pyflakes" +version = "3.2.0" +description = "passive checker of Python programs" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"}, + {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"}, +] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "0ef693f84017d54b0ea1d26b405d979fc57973167b307ccd9ed780ad9bdc1a3a" +content-hash = "01960a0fd26ee6cb565391f829bae290abbf990c15496a4c93b323b0253a09fa" diff --git a/pyproject.toml b/pyproject.toml index b4894b6..4684745 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,10 @@ python = "^3.9" [tool.poetry.group.dev.dependencies] +# Code formatters +autoflake = "^2.3" +black = "^24.8" +isort = "^5.13" [tool.poetry.urls] @@ -40,6 +44,57 @@ python = "^3.9" +[tool.autoflake] +# Source: https://github.com/PyCQA/autoflake#configuration + +in-place = true +recursive = true +expand-star-imports = true +remove-all-unused-imports = true +ignore-init-module-imports = true # modifies "remove-all-unused-imports" +remove-duplicate-keys = true +remove-unused-variables = true + + + +[tool.black] +# Source: https://black.readthedocs.io/en/stable/usage_and_configuration/the_basics.html + +line-length = 88 +target-version = ["py312", "py311", "py310", "py39"] + + + +[tool.isort] +# Source: https://pycqa.github.io/isort/docs/configuration/options.html + +known_first_party = ["lalib"] + +atomic = true +case_sensitive = true +combine_star = true +force_alphabetical_sort_within_sections = true +lines_after_imports = 2 +remove_redundant_aliases = true + +# Comply with black's style => Instead of: 'profile = "black"' +# Source: https://pycqa.github.io/isort/docs/configuration/profiles.html +ensure_newline_before_comments = true +force_grid_wrap = 0 +include_trailing_comma = true +line_length = 88 +multi_line_output = 3 +split_on_trailing_comma = true +use_parentheses = true + +# Comply with Google's Python style guide +# => All imports go on a single line (with some exceptions) +# Source: https://google.github.io/styleguide/pyguide.html#313-imports-formatting +force_single_line = true +single_line_exclusions = ["collections.abc", "typing"] + + + [build-system] requires = ["poetry-core"]